Initiale Version

This commit is contained in:
Jan Unger
2016-08-16 21:20:53 +02:00
commit 88cf71d772
10930 changed files with 1708903 additions and 0 deletions

View File

@@ -0,0 +1,466 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\DistributionBundle\Composer;
use Symfony\Component\ClassLoader\ClassCollectionLoader;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\PhpExecutableFinder;
use Composer\Script\Event;
/**
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class ScriptHandler
{
/**
* Composer variables are declared static so that an event could update
* a composer.json and set new options, making them immediately available
* to forthcoming listeners.
*/
protected static $options = array(
'symfony-app-dir' => 'app',
'symfony-web-dir' => 'web',
'symfony-assets-install' => 'hard',
'symfony-cache-warmup' => false,
);
/**
* Asks if the new directory structure should be used, installs the structure if needed.
*
* @param Event $event
*/
public static function defineDirectoryStructure(Event $event)
{
$options = static::getOptions($event);
if (!getenv('SENSIOLABS_ENABLE_NEW_DIRECTORY_STRUCTURE') || !$event->getIO()->askConfirmation('Would you like to use Symfony 3 directory structure? [y/N] ', false)) {
return;
}
$rootDir = getcwd();
$appDir = $options['symfony-app-dir'];
$webDir = $options['symfony-web-dir'];
$binDir = static::$options['symfony-bin-dir'] = 'bin';
$varDir = static::$options['symfony-var-dir'] = 'var';
static::updateDirectoryStructure($event, $rootDir, $appDir, $binDir, $varDir, $webDir);
}
/**
* Builds the bootstrap file.
*
* The bootstrap file contains PHP file that are always needed by the application.
* It speeds up the application bootstrapping.
*
* @param Event $event
*/
public static function buildBootstrap(Event $event)
{
$options = static::getOptions($event);
$bootstrapDir = $autoloadDir = $options['symfony-app-dir'];
if (static::useNewDirectoryStructure($options)) {
$bootstrapDir = $options['symfony-var-dir'];
if (!static::hasDirectory($event, 'symfony-var-dir', $bootstrapDir, 'build bootstrap file')) {
return;
}
}
if (!static::hasDirectory($event, 'symfony-app-dir', $autoloadDir, 'build bootstrap file')) {
return;
}
static::executeBuildBootstrap($event, $bootstrapDir, $autoloadDir, $options['process-timeout']);
}
protected static function hasDirectory(Event $event, $configName, $path, $actionName)
{
if (!is_dir($path)) {
$event->getIO()->write(sprintf('The %s (%s) specified in composer.json was not found in %s, can not %s.', $configName, $path, getcwd(), $actionName));
return false;
}
return true;
}
/**
* Sets up deployment target specific features.
* Could be custom web server configs, boot command files etc.
*
* @param Event $event
*/
public static function prepareDeploymentTarget(Event $event)
{
static::prepareDeploymentTargetHeroku($event);
}
protected static function prepareDeploymentTargetHeroku(Event $event)
{
$options = static::getOptions($event);
if (($stack = getenv('STACK')) && ($stack == 'cedar' || $stack == 'cedar-14')) {
$fs = new Filesystem();
if (!$fs->exists('Procfile')) {
$event->getIO()->write('Heroku deploy detected; creating default Procfile for "web" dyno');
$fs->dumpFile('Procfile', sprintf('web: $(composer config bin-dir)/heroku-php-apache2 %s/', $options['symfony-web-dir']));
}
}
}
/**
* Clears the Symfony cache.
*
* @param Event $event
*/
public static function clearCache(Event $event)
{
$options = static::getOptions($event);
$consoleDir = static::getConsoleDir($event, 'clear the cache');
if (null === $consoleDir) {
return;
}
$warmup = '';
if (!$options['symfony-cache-warmup']) {
$warmup = ' --no-warmup';
}
static::executeCommand($event, $consoleDir, 'cache:clear'.$warmup, $options['process-timeout']);
}
/**
* Installs the assets under the web root directory.
*
* For better interoperability, assets are copied instead of symlinked by default.
*
* Even if symlinks work on Windows, this is only true on Windows Vista and later,
* but then, only when running the console with admin rights or when disabling the
* strict user permission checks (which can be done on Windows 7 but not on Windows
* Vista).
*
* @param Event $event
*/
public static function installAssets(Event $event)
{
$options = static::getOptions($event);
$consoleDir = static::getConsoleDir($event, 'install assets');
if (null === $consoleDir) {
return;
}
$webDir = $options['symfony-web-dir'];
$symlink = '';
if ($options['symfony-assets-install'] == 'symlink') {
$symlink = '--symlink ';
} elseif ($options['symfony-assets-install'] == 'relative') {
$symlink = '--symlink --relative ';
}
if (!static::hasDirectory($event, 'symfony-web-dir', $webDir, 'install assets')) {
return;
}
static::executeCommand($event, $consoleDir, 'assets:install '.$symlink.escapeshellarg($webDir), $options['process-timeout']);
}
/**
* Updated the requirements file.
*
* @param Event $event
*/
public static function installRequirementsFile(Event $event)
{
$options = static::getOptions($event);
$appDir = $options['symfony-app-dir'];
$fs = new Filesystem();
$newDirectoryStructure = static::useNewDirectoryStructure($options);
if (!$newDirectoryStructure) {
if (!static::hasDirectory($event, 'symfony-app-dir', $appDir, 'install the requirements files')) {
return;
}
$fs->copy(__DIR__.'/../Resources/skeleton/app/SymfonyRequirements.php', $appDir.'/SymfonyRequirements.php', true);
$fs->copy(__DIR__.'/../Resources/skeleton/app/check.php', $appDir.'/check.php', true);
} else {
$binDir = $options['symfony-bin-dir'];
$varDir = $options['symfony-var-dir'];
if (!static::hasDirectory($event, 'symfony-var-dir', $varDir, 'install the requirements files')) {
return;
}
if (!static::hasDirectory($event, 'symfony-bin-dir', $binDir, 'install the requirements files')) {
return;
}
$fs->copy(__DIR__.'/../Resources/skeleton/app/SymfonyRequirements.php', $varDir.'/SymfonyRequirements.php', true);
$fs->copy(__DIR__.'/../Resources/skeleton/app/check.php', $binDir.'/symfony_requirements', true);
$fs->remove(array($appDir.'/check.php', $appDir.'/SymfonyRequirements.php', true));
$fs->dumpFile($binDir.'/symfony_requirements', '#!/usr/bin/env php'."\n".str_replace(".'/SymfonyRequirements.php'", ".'/".$fs->makePathRelative($varDir, $binDir)."SymfonyRequirements.php'", file_get_contents($binDir.'/symfony_requirements')));
$fs->chmod($binDir.'/symfony_requirements', 0755);
}
$webDir = $options['symfony-web-dir'];
// if the user has already removed the config.php file, do nothing
// as the file must be removed for production use
if ($fs->exists($webDir.'/config.php')) {
if (!$newDirectoryStructure) {
$fs->copy(__DIR__.'/../Resources/skeleton/web/config.php', $webDir.'/config.php', true);
} else {
$fs->dumpFile($webDir.'/config.php', str_replace('/../app/SymfonyRequirements.php', '/'.$fs->makePathRelative($varDir, $webDir).'SymfonyRequirements.php', file_get_contents(__DIR__.'/../Resources/skeleton/web/config.php')));
}
}
}
public static function removeSymfonyStandardFiles(Event $event)
{
$options = static::getOptions($event);
$appDir = $options['symfony-app-dir'];
if (!is_dir($appDir)) {
return;
}
if (!is_dir($appDir.'/SymfonyStandard')) {
return;
}
$fs = new Filesystem();
$fs->remove($appDir.'/SymfonyStandard');
}
public static function doBuildBootstrap($bootstrapDir)
{
$file = $bootstrapDir.'/bootstrap.php.cache';
if (file_exists($file)) {
unlink($file);
}
$classes = array(
'Symfony\\Component\\HttpFoundation\\ParameterBag',
'Symfony\\Component\\HttpFoundation\\HeaderBag',
'Symfony\\Component\\HttpFoundation\\FileBag',
'Symfony\\Component\\HttpFoundation\\ServerBag',
'Symfony\\Component\\HttpFoundation\\Request',
'Symfony\\Component\\HttpFoundation\\Response',
'Symfony\\Component\\HttpFoundation\\ResponseHeaderBag',
'Symfony\\Component\\DependencyInjection\\ContainerAwareInterface',
'Symfony\\Component\\DependencyInjection\\Container',
'Symfony\\Component\\HttpKernel\\Kernel',
'Symfony\\Component\\ClassLoader\\ClassCollectionLoader',
'Symfony\\Component\\ClassLoader\\ApcClassLoader',
'Symfony\\Component\\HttpKernel\\Bundle\\Bundle',
'Symfony\\Component\\Config\\ConfigCache',
// cannot be included as commands are discovered based on the path to this class via Reflection
//'Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle',
);
// introspect the autoloader to get the right file
// we cannot use class_exist() here as it would load the class
// which won't be included into the cache then.
// we know that composer autoloader is first (see bin/build_bootstrap.php)
$autoloaders = spl_autoload_functions();
if (is_array($autoloaders[0]) && method_exists($autoloaders[0][0], 'findFile') && $autoloaders[0][0]->findFile('Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel')) {
$classes[] = 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel';
} else {
$classes[] = 'Symfony\\Component\\HttpKernel\\HttpKernel';
}
ClassCollectionLoader::load($classes, dirname($file), basename($file, '.php.cache'), false, false, '.php.cache');
$bootstrapContent = substr(file_get_contents($file), 5);
file_put_contents($file, sprintf(<<<'EOF'
<?php
%s
EOF
, $bootstrapContent));
}
protected static function executeCommand(Event $event, $consoleDir, $cmd, $timeout = 300)
{
$php = escapeshellarg(static::getPhp(false));
$phpArgs = implode(' ', array_map('escapeshellarg', static::getPhpArguments()));
$console = escapeshellarg($consoleDir.'/console');
if ($event->getIO()->isDecorated()) {
$console .= ' --ansi';
}
$process = new Process($php.($phpArgs ? ' '.$phpArgs : '').' '.$console.' '.$cmd, null, null, null, $timeout);
$process->run(function ($type, $buffer) use ($event) { $event->getIO()->write($buffer, false); });
if (!$process->isSuccessful()) {
throw new \RuntimeException(sprintf("An error occurred when executing the \"%s\" command:\n\n%s\n\n%s.", escapeshellarg($cmd), $process->getOutput(), $process->getErrorOutput()));
}
}
protected static function executeBuildBootstrap(Event $event, $bootstrapDir, $autoloadDir, $timeout = 300)
{
$php = escapeshellarg(static::getPhp(false));
$phpArgs = implode(' ', array_map('escapeshellarg', static::getPhpArguments()));
$cmd = escapeshellarg(__DIR__.'/../Resources/bin/build_bootstrap.php');
$bootstrapDir = escapeshellarg($bootstrapDir);
$autoloadDir = escapeshellarg($autoloadDir);
$useNewDirectoryStructure = '';
if (static::useNewDirectoryStructure(static::getOptions($event))) {
$useNewDirectoryStructure = escapeshellarg('--use-new-directory-structure');
}
$process = new Process($php.($phpArgs ? ' '.$phpArgs : '').' '.$cmd.' '.$bootstrapDir.' '.$autoloadDir.' '.$useNewDirectoryStructure, getcwd(), null, null, $timeout);
$process->run(function ($type, $buffer) use ($event) { $event->getIO()->write($buffer, false); });
if (!$process->isSuccessful()) {
throw new \RuntimeException('An error occurred when generating the bootstrap file.');
}
}
protected static function updateDirectoryStructure(Event $event, $rootDir, $appDir, $binDir, $varDir, $webDir)
{
$event->getIO()->write('Updating Symfony directory structure...');
$fs = new Filesystem();
$fs->mkdir(array($binDir, $varDir));
foreach (array(
$appDir.'/console' => $binDir.'/console',
$appDir.'/phpunit.xml.dist' => $rootDir.'/phpunit.xml.dist',
) as $source => $target) {
$fs->rename($source, $target, true);
}
foreach (array('/logs', '/cache') as $dir) {
$fs->rename($appDir.$dir, $varDir.$dir);
}
$gitignore = <<<EOF
/web/bundles/
/app/config/parameters.yml
/var/bootstrap.php.cache
/var/SymfonyRequirements.php
/var/cache/*
/var/logs/*
!var/cache/.gitkeep
!var/logs/.gitkeep
/build/
/vendor/
/bin/*
!bin/console
!bin/symfony_requirements
/composer.phar
EOF;
$phpunitKernelBefore = <<<EOF
<!--
<php>
<server name="KERNEL_DIR" value="/path/to/your/app/" />
</php>
-->
EOF;
$phpunitKernelAfter = <<<EOF
<php>
<server name="KERNEL_DIR" value="$appDir/" />
</php>
EOF;
$phpunit = str_replace(array('<directory>../src', '"bootstrap.php.cache"', $phpunitKernelBefore), array('<directory>src', '"'.$varDir.'/bootstrap.php.cache"', $phpunitKernelAfter), file_get_contents($rootDir.'/phpunit.xml.dist'));
$composer = str_replace('"symfony-app-dir": "app",', "\"symfony-app-dir\": \"app\",\n \"symfony-bin-dir\": \"bin\",\n \"symfony-var-dir\": \"var\",", file_get_contents($rootDir.'/composer.json'));
$fs->dumpFile($webDir.'/app.php', str_replace($appDir.'/bootstrap.php.cache', $varDir.'/bootstrap.php.cache', file_get_contents($webDir.'/app.php')));
$fs->dumpFile($webDir.'/app_dev.php', str_replace($appDir.'/bootstrap.php.cache', $varDir.'/bootstrap.php.cache', file_get_contents($webDir.'/app_dev.php')));
$fs->dumpFile($binDir.'/console', str_replace(array(".'/bootstrap.php.cache'", ".'/AppKernel.php'"), array(".'/".$fs->makePathRelative($varDir, $binDir)."bootstrap.php.cache'", ".'/".$fs->makePathRelative($appDir, $binDir)."AppKernel.php'"), file_get_contents($binDir.'/console')));
$fs->dumpFile($rootDir.'/phpunit.xml.dist', $phpunit);
$fs->dumpFile($rootDir.'/composer.json', $composer);
$fs->dumpFile($rootDir.'/.gitignore', $gitignore);
$fs->chmod($binDir.'/console', 0755);
}
protected static function getOptions(Event $event)
{
$options = array_merge(static::$options, $event->getComposer()->getPackage()->getExtra());
$options['symfony-assets-install'] = getenv('SYMFONY_ASSETS_INSTALL') ?: $options['symfony-assets-install'];
$options['process-timeout'] = $event->getComposer()->getConfig()->get('process-timeout');
return $options;
}
protected static function getPhp($includeArgs = true)
{
$phpFinder = new PhpExecutableFinder();
if (!$phpPath = $phpFinder->find($includeArgs)) {
throw new \RuntimeException('The php executable could not be found, add it to your PATH environment variable and try again');
}
return $phpPath;
}
protected static function getPhpArguments()
{
$arguments = array();
$phpFinder = new PhpExecutableFinder();
if (method_exists($phpFinder, 'findArguments')) {
$arguments = $phpFinder->findArguments();
}
if (false !== $ini = php_ini_loaded_file()) {
$arguments[] = '--php-ini='.$ini;
}
return $arguments;
}
/**
* Returns a relative path to the directory that contains the `console` command.
*
* @param Event $event The command event.
* @param string $actionName The name of the action
*
* @return string|null The path to the console directory, null if not found.
*/
protected static function getConsoleDir(Event $event, $actionName)
{
$options = static::getOptions($event);
if (static::useNewDirectoryStructure($options)) {
if (!static::hasDirectory($event, 'symfony-bin-dir', $options['symfony-bin-dir'], $actionName)) {
return;
}
return $options['symfony-bin-dir'];
}
if (!static::hasDirectory($event, 'symfony-app-dir', $options['symfony-app-dir'], 'execute command')) {
return;
}
return $options['symfony-app-dir'];
}
/**
* Returns true if the new directory structure is used.
*
* @param array $options Composer options
*
* @return bool
*/
protected static function useNewDirectoryStructure(array $options)
{
return isset($options['symfony-var-dir']) && is_dir($options['symfony-var-dir']);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Sensio\Bundle\DistributionBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\Config\FileLocator;
/**
* SensioDistributionExtension.
*
* @author Marc Weistroff <marc.weistroff@sensio.com>
*/
class SensioDistributionExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('security.xml');
}
public function getNamespace()
{
return 'http://symfony.com/schema/dic/symfony/sensiodistribution';
}
public function getAlias()
{
return 'sensio_distribution';
}
}

View File

@@ -0,0 +1,19 @@
Copyright (c) 2010,2013 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,27 @@
SensioDistributionBundle
========================
SensioDistributionBundle provides useful developer features that can be re-used
amongst several Symfony Distributions.
Composer Hooks
--------------
The bundle hooks up into the Composer process to automate the following actions
when running an install or an update:
* Update the ``bootstrap.php.cache`` file (and clears the cache);
* Install the assets under the web root directory;
* Update the requirements file.
Security
--------
The bundle includes the SensioLabs Security Checker. When included in a Symfony
application, the check is available:
.. code-block:: bash
$ ./app/console security:check

View File

@@ -0,0 +1,140 @@
#!/bin/bash
# This file is part of the Symfony Standard Edition.
#
# (c) Fabien Potencier <fabien@symfony.com>
#
# For the full copyright and license information, please view the LICENSE
# file that was distributed with this source code.
if [ ! $1 ]; then
echo "\033[37;41mYou must pass the build dir as an absolute path\033[0m"
exit 1
fi
if [ ! $2 ]; then
echo "\033[37;41mYou must pass the version to build\033[0m"
exit 1
fi
DIR=$1
CURRENT=`php -r "echo realpath(dirname(\\$_SERVER['argv'][0]));"`
if [[ ! "$DIR" = /* ]]; then
DIR="$CURRENT/$DIR"
fi
if [ ! -d $DIR ]; then
echo "\033[37;41mThe build dir does not exist\033[0m"
exit 1
fi
# avoid the creation of ._* files
export COPY_EXTENDED_ATTRIBUTES_DISABLE=true
export COPYFILE_DISABLE=true
# Temp dir
rm -rf /tmp/Symfony
mkdir /tmp/Symfony
# Create project
composer create-project --prefer-dist --no-interaction symfony/framework-standard-edition /tmp/Symfony $2
if [ 0 -ne $? ]; then
echo "\033[37;41mVersion $2 does not exist\033[0m"
exit 1
fi
cd /tmp/Symfony
# cleanup
rm -rf app/cache/* app/logs/* .git*
chmod 777 app/cache app/logs
find . -name .DS_Store | xargs rm -rf -
VERSION=`grep ' VERSION ' vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Kernel.php | sed -E "s/.*'(.+)'.*/\1/g"`
# With vendors
cd /tmp/Symfony
TARGET=/tmp/Symfony/vendor
# Doctrine
cd $TARGET/doctrine/orm && rm -rf UPGRADE* build* tests tools lib/vendor
cd $TARGET/doctrine/dbal && rm -rf build* tests lib/vendor
cd $TARGET/doctrine/common && rm -rf build* tests lib/vendor
if [ -d $TARGET/doctrine/doctrine-bundle/Doctrine/Bundle/DoctrineBundle ]; then
cd $TARGET/doctrine/doctrine-bundle/Doctrine/Bundle/DoctrineBundle && rm -rf Tests Resources/doc
else
cd $TARGET/doctrine/doctrine-bundle && rm -rf Tests Resources/doc
fi
# kriswallsmith
if [ -d $TARGET/kriswallsmith/assetic ]; then
cd $TARGET/kriswallsmith/assetic && rm -rf CHANGELOG* phpunit.xml* tests docs
fi
# Monolog
cd $TARGET/monolog/monolog && rm -rf README.markdown phpunit.xml* tests
# Sensio
if [ -d $TARGET/sensio/distribution-bundle/Sensio/Bundle/DistributionBundle ]; then
cd $TARGET/sensio/distribution-bundle/Sensio/Bundle/DistributionBundle && rm -rf phpunit.xml* Tests CHANGELOG* Resources/doc
else
cd $TARGET/sensio/distribution-bundle && rm -rf phpunit.xml* Tests CHANGELOG* Resources/doc
fi
if [ -d $TARGET/sensio/framework-extra-bundle/Sensio/Bundle/FrameworkExtraBundle ]; then
cd $TARGET/sensio/framework-extra-bundle/Sensio/Bundle/FrameworkExtraBundle && rm -rf phpunit.xml* Tests CHANGELOG* Resources/doc
else
cd $TARGET/sensio/framework-extra-bundle && rm -rf phpunit.xml* Tests CHANGELOG* Resources/doc
fi
if [ -d $TARGET/sensio/generator-bundle/Sensio/Bundle/GeneratorBundle ]; then
cd $TARGET/sensio/generator-bundle/Sensio/Bundle/GeneratorBundle && rm -rf phpunit.xml* Tests CHANGELOG* Resources/doc
else
cd $TARGET/sensio/generator-bundle && rm -rf phpunit.xml* Tests CHANGELOG* Resources/doc
fi
# Swiftmailer
cd $TARGET/swiftmailer/swiftmailer && rm -rf CHANGES README* build* docs notes test-suite tests create_pear_package.php package*
# Symfony
cd $TARGET/symfony/symfony && rm -rf README.md phpunit.xml* tests *.sh vendor
if [ -d $TARGET/symfony/assetic-bundle/Symfony/Bundle/AsseticBundle ]; then
cd $TARGET/symfony/assetic-bundle/Symfony/Bundle/AsseticBundle && rm -rf Tests Resources/doc
elif [ -d $TARGET/symfony/assetic-bundle ]; then
cd $TARGET/symfony/assetic-bundle && rm -rf Tests Resources/doc
fi
if [ -d $TARGET/symfony/swiftmailer-bundle/Symfony/Bundle/SwiftmailerBundle ]; then
cd $TARGET/symfony/swiftmailer-bundle/Symfony/Bundle/SwiftmailerBundle && rm -rf Tests Resources/doc
else
cd $TARGET/symfony/swiftmailer-bundle && rm -rf Tests Resources/doc
fi
if [ -d $TARGET/symfony/monolog-bundle/Symfony/Bundle/MonologBundle ]; then
cd $TARGET/symfony/monolog-bundle/Symfony/Bundle/MonologBundle && rm -rf Tests Resources/doc
else
cd $TARGET/symfony/monolog-bundle && rm -rf Tests Resources/doc
fi
# Twig
cd $TARGET/twig/twig && rm -rf AUTHORS CHANGELOG README.markdown bin doc package.xml.tpl phpunit.xml* test
# cleanup
find $TARGET -name .git | xargs rm -rf -
find $TARGET -name .gitignore | xargs rm -rf -
find $TARGET -name .gitmodules | xargs rm -rf -
find $TARGET -name .svn | xargs rm -rf -
# With vendors
cd /tmp
tar zcpf $DIR/Symfony_Standard_Vendors_$VERSION.tgz Symfony
rm -f $DIR/Symfony_Standard_Vendors_$VERSION.zip
zip -rq $DIR/Symfony_Standard_Vendors_$VERSION.zip Symfony
# Without vendors
cd /tmp
rm -rf Symfony/vendor
tar zcpf $DIR/Symfony_Standard_$VERSION.tgz Symfony
rm -f $DIR/Symfony_Standard_$VERSION.zip
zip -rq $DIR/Symfony_Standard_$VERSION.zip Symfony

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env php
<?php
/*
* This file is part of the Symfony Standard Edition.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Sensio\Bundle\DistributionBundle\Composer\ScriptHandler;
if (PHP_SAPI !== 'cli') {
echo 'Warning: '.__FILE__.' should be invoked via the CLI version of PHP, not the '.PHP_SAPI.' SAPI'.PHP_EOL;
}
function getRealpath($path, $message = 'Directory %s does not seem to be valid.')
{
if (!$path = realpath($path)) {
exit(sprintf($message, $path));
}
return $path;
}
$argv = $_SERVER['argv'];
$autoloadDir = $bootstrapDir = null;
$useNewDirectoryStructure = false;
// allow the base path to be passed as the first argument, or default
if (!empty($argv[1])) {
$bootstrapDir = getRealpath($argv[1]);
}
if (!empty($argv[2])) {
$autoloadDir = getRealpath($argv[2]);
}
if (!empty($argv[3])) {
$useNewDirectoryStructure = true;
}
$rootDir = __DIR__.'/../../../../..';
if (null === $autoloadDir) {
$autoloadDir = getRealpath($rootDir.'/app', 'Looks like you don\'t have a standard layout.');
}
if (null === $bootstrapDir) {
$bootstrapDir = $autoloadDir;
if ($useNewDirectoryStructure) {
$bootstrapDir = getRealpath($rootDir.'/var');
}
}
require_once $autoloadDir.'/autoload.php';
// here we pass realpaths as resolution between absolute and relative path can be wrong
ScriptHandler::doBuildBootstrap($bootstrapDir);

View File

@@ -0,0 +1,112 @@
#!/bin/bash
# This file is part of the Symfony Standard Edition.
#
# (c) Fabien Potencier <fabien@symfony.com>
#
# For the full copyright and license information, please view the LICENSE
# file that was distributed with this source code.
if [ ! $1 ]; then
echo "\033[37;41mYou must pass the build dir as an absolute path\033[0m"
exit 1
fi
DIR=$1
CURRENT=`php -r "echo realpath(dirname(\\$_SERVER['argv'][0]));"`
if [[ ! "$DIR" = /* ]]; then
DIR="$CURRENT/$DIR"
fi
if [ ! -d $DIR ]; then
echo "\033[37;41mThe build dir does not exist\033[0m"
exit 1
fi
# avoid the creation of ._* files
export COPY_EXTENDED_ATTRIBUTES_DISABLE=true
export COPYFILE_DISABLE=true
# Prepare temp. dir
rm -rf /tmp/Symfony
mkdir /tmp/Symfony
# Clone demo application and install its dependencies
git clone https://github.com/symfony/symfony-demo /tmp/Symfony
cd /tmp/Symfony
composer install --prefer-dist --no-interaction --ignore-platform-reqs --no-plugins --optimize-autoloader
# cleanup
cd /tmp/Symfony
rm -f UPGRADE*
mv .gitignore keep.gitignore
rm -rf app/cache/* app/logs/* .git*
mv keep.gitignore .gitignore
chmod 777 app/cache app/logs
find . -name .DS_Store | xargs rm -rf -
# remove unneded dependencies files
cd /tmp/Symfony
TARGET=/tmp/Symfony/vendor
# Doctrine
cd $TARGET/doctrine/orm && rm -rf UPGRADE* build* bin tests tools lib/vendor
cd $TARGET/doctrine/dbal && rm -rf bin build* tests lib/vendor
cd $TARGET/doctrine/common && rm -rf build* tests lib/vendor
if [ -d $TARGET/doctrine/doctrine-bundle/Doctrine/Bundle/DoctrineBundle ]; then
cd $TARGET/doctrine/doctrine-bundle/Doctrine/Bundle/DoctrineBundle && rm -rf Tests Resources/doc
else
cd $TARGET/doctrine/doctrine-bundle && rm -rf Tests Resources/doc
fi
# kriswallsmith
cd $TARGET/kriswallsmith/assetic && rm -rf CHANGELOG* phpunit.xml* tests docs
# Monolog
cd $TARGET/monolog/monolog && rm -rf README.markdown phpunit.xml* tests
# Sensio
cd $TARGET/sensio/distribution-bundle/Sensio/Bundle/DistributionBundle && rm -rf phpunit.xml* Tests CHANGELOG* Resources/doc
cd $TARGET/sensio/framework-extra-bundle/Sensio/Bundle/FrameworkExtraBundle && rm -rf phpunit.xml* Tests CHANGELOG* Resources/doc
cd $TARGET/sensio/generator-bundle/Sensio/Bundle/GeneratorBundle && rm -rf phpunit.xml* Tests CHANGELOG* Resources/doc
# Swiftmailer
cd $TARGET/swiftmailer/swiftmailer && rm -rf CHANGES README* build* docs notes test-suite tests create_pear_package.php package*
# Symfony
cd $TARGET/symfony/symfony && rm -rf README.md phpunit.xml* tests *.sh vendor
if [ -d $TARGET/symfony/assetic-bundle/Symfony/Bundle/AsseticBundle ]; then
cd $TARGET/symfony/assetic-bundle/Symfony/Bundle/AsseticBundle && rm -rf Tests Resources/doc
else
cd $TARGET/symfony/assetic-bundle && rm -rf Tests Resources/doc
fi
if [ -d $TARGET/symfony/swiftmailer-bundle/Symfony/Bundle/SwiftmailerBundle ]; then
cd $TARGET/symfony/swiftmailer-bundle/Symfony/Bundle/SwiftmailerBundle && rm -rf Tests Resources/doc
else
cd $TARGET/symfony/swiftmailer-bundle && rm -rf Tests Resources/doc
fi
if [ -d $TARGET/symfony/monolog-bundle/Symfony/Bundle/MonologBundle ]; then
cd $TARGET/symfony/monolog-bundle/Symfony/Bundle/MonologBundle && rm -rf Tests Resources/doc
else
cd $TARGET/symfony/monolog-bundle && rm -rf Tests Resources/doc
fi
# Twig
cd $TARGET/twig/twig && rm -rf AUTHORS CHANGELOG README.markdown bin doc package.xml.tpl phpunit.xml* test
cd $TARGET/twig/extensions && rm -rf README doc phpunit.xml* test
# final cleanup
find $TARGET -name .git | xargs rm -rf -
find $TARGET -name .gitignore | xargs rm -rf -
find $TARGET -name .gitmodules | xargs rm -rf -
find $TARGET -name .svn | xargs rm -rf -
# build ZIP and TGZ packages
cd /tmp
tar zcpf $DIR/Symfony_Demo.tgz Symfony
rm -f $DIR/Symfony_Demo.zip
zip -rq $DIR/Symfony_Demo.zip Symfony

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="sensio_distribution.security_checker" class="SensioLabs\Security\SecurityChecker" />
<service id="sensio_distribution.security_checker.command" class="SensioLabs\Security\Command\SecurityCheckerCommand">
<argument type="service" id="sensio_distribution.security_checker" />
<tag name="console.command" />
</service>
</services>
</container>

View File

@@ -0,0 +1,774 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/*
* Users of PHP 5.2 should be able to run the requirements checks.
* This is why the file and all classes must be compatible with PHP 5.2+
* (e.g. not using namespaces and closures).
*
* ************** CAUTION **************
*
* DO NOT EDIT THIS FILE as it will be overridden by Composer as part of
* the installation/update process. The original file resides in the
* SensioDistributionBundle.
*
* ************** CAUTION **************
*/
/**
* Represents a single PHP requirement, e.g. an installed extension.
* It can be a mandatory requirement or an optional recommendation.
* There is a special subclass, named PhpIniRequirement, to check a php.ini configuration.
*
* @author Tobias Schultze <http://tobion.de>
*/
class Requirement
{
private $fulfilled;
private $testMessage;
private $helpText;
private $helpHtml;
private $optional;
/**
* Constructor that initializes the requirement.
*
* @param bool $fulfilled Whether the requirement is fulfilled
* @param string $testMessage The message for testing the requirement
* @param string $helpHtml The help text formatted in HTML for resolving the problem
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
* @param bool $optional Whether this is only an optional recommendation not a mandatory requirement
*/
public function __construct($fulfilled, $testMessage, $helpHtml, $helpText = null, $optional = false)
{
$this->fulfilled = (bool) $fulfilled;
$this->testMessage = (string) $testMessage;
$this->helpHtml = (string) $helpHtml;
$this->helpText = null === $helpText ? strip_tags($this->helpHtml) : (string) $helpText;
$this->optional = (bool) $optional;
}
/**
* Returns whether the requirement is fulfilled.
*
* @return bool true if fulfilled, otherwise false
*/
public function isFulfilled()
{
return $this->fulfilled;
}
/**
* Returns the message for testing the requirement.
*
* @return string The test message
*/
public function getTestMessage()
{
return $this->testMessage;
}
/**
* Returns the help text for resolving the problem.
*
* @return string The help text
*/
public function getHelpText()
{
return $this->helpText;
}
/**
* Returns the help text formatted in HTML.
*
* @return string The HTML help
*/
public function getHelpHtml()
{
return $this->helpHtml;
}
/**
* Returns whether this is only an optional recommendation and not a mandatory requirement.
*
* @return bool true if optional, false if mandatory
*/
public function isOptional()
{
return $this->optional;
}
}
/**
* Represents a PHP requirement in form of a php.ini configuration.
*
* @author Tobias Schultze <http://tobion.de>
*/
class PhpIniRequirement extends Requirement
{
/**
* Constructor that initializes the requirement.
*
* @param string $cfgName The configuration name used for ini_get()
* @param bool|callback $evaluation Either a boolean indicating whether the configuration should evaluate to true or false,
* or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement
* @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false.
* This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin.
* Example: You require a config to be true but PHP later removes this config and defaults it to true internally.
* @param string|null $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived)
* @param string|null $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a boolean a default help is derived)
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
* @param bool $optional Whether this is only an optional recommendation not a mandatory requirement
*/
public function __construct($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null, $optional = false)
{
$cfgValue = ini_get($cfgName);
if (is_callable($evaluation)) {
if (null === $testMessage || null === $helpHtml) {
throw new InvalidArgumentException('You must provide the parameters testMessage and helpHtml for a callback evaluation.');
}
$fulfilled = call_user_func($evaluation, $cfgValue);
} else {
if (null === $testMessage) {
$testMessage = sprintf('%s %s be %s in php.ini',
$cfgName,
$optional ? 'should' : 'must',
$evaluation ? 'enabled' : 'disabled'
);
}
if (null === $helpHtml) {
$helpHtml = sprintf('Set <strong>%s</strong> to <strong>%s</strong> in php.ini<a href="#phpini">*</a>.',
$cfgName,
$evaluation ? 'on' : 'off'
);
}
$fulfilled = $evaluation == $cfgValue;
}
parent::__construct($fulfilled || ($approveCfgAbsence && false === $cfgValue), $testMessage, $helpHtml, $helpText, $optional);
}
}
/**
* A RequirementCollection represents a set of Requirement instances.
*
* @author Tobias Schultze <http://tobion.de>
*/
class RequirementCollection implements IteratorAggregate
{
private $requirements = array();
/**
* Gets the current RequirementCollection as an Iterator.
*
* @return Traversable A Traversable interface
*/
public function getIterator()
{
return new ArrayIterator($this->requirements);
}
/**
* Adds a Requirement.
*
* @param Requirement $requirement A Requirement instance
*/
public function add(Requirement $requirement)
{
$this->requirements[] = $requirement;
}
/**
* Adds a mandatory requirement.
*
* @param bool $fulfilled Whether the requirement is fulfilled
* @param string $testMessage The message for testing the requirement
* @param string $helpHtml The help text formatted in HTML for resolving the problem
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
*/
public function addRequirement($fulfilled, $testMessage, $helpHtml, $helpText = null)
{
$this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, false));
}
/**
* Adds an optional recommendation.
*
* @param bool $fulfilled Whether the recommendation is fulfilled
* @param string $testMessage The message for testing the recommendation
* @param string $helpHtml The help text formatted in HTML for resolving the problem
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
*/
public function addRecommendation($fulfilled, $testMessage, $helpHtml, $helpText = null)
{
$this->add(new Requirement($fulfilled, $testMessage, $helpHtml, $helpText, true));
}
/**
* Adds a mandatory requirement in form of a php.ini configuration.
*
* @param string $cfgName The configuration name used for ini_get()
* @param bool|callback $evaluation Either a boolean indicating whether the configuration should evaluate to true or false,
* or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement
* @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false.
* This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin.
* Example: You require a config to be true but PHP later removes this config and defaults it to true internally.
* @param string $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived)
* @param string $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a boolean a default help is derived)
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
*/
public function addPhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null)
{
$this->add(new PhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, false));
}
/**
* Adds an optional recommendation in form of a php.ini configuration.
*
* @param string $cfgName The configuration name used for ini_get()
* @param bool|callback $evaluation Either a boolean indicating whether the configuration should evaluate to true or false,
* or a callback function receiving the configuration value as parameter to determine the fulfillment of the requirement
* @param bool $approveCfgAbsence If true the Requirement will be fulfilled even if the configuration option does not exist, i.e. ini_get() returns false.
* This is helpful for abandoned configs in later PHP versions or configs of an optional extension, like Suhosin.
* Example: You require a config to be true but PHP later removes this config and defaults it to true internally.
* @param string $testMessage The message for testing the requirement (when null and $evaluation is a boolean a default message is derived)
* @param string $helpHtml The help text formatted in HTML for resolving the problem (when null and $evaluation is a boolean a default help is derived)
* @param string|null $helpText The help text (when null, it will be inferred from $helpHtml, i.e. stripped from HTML tags)
*/
public function addPhpIniRecommendation($cfgName, $evaluation, $approveCfgAbsence = false, $testMessage = null, $helpHtml = null, $helpText = null)
{
$this->add(new PhpIniRequirement($cfgName, $evaluation, $approveCfgAbsence, $testMessage, $helpHtml, $helpText, true));
}
/**
* Adds a requirement collection to the current set of requirements.
*
* @param RequirementCollection $collection A RequirementCollection instance
*/
public function addCollection(RequirementCollection $collection)
{
$this->requirements = array_merge($this->requirements, $collection->all());
}
/**
* Returns both requirements and recommendations.
*
* @return array Array of Requirement instances
*/
public function all()
{
return $this->requirements;
}
/**
* Returns all mandatory requirements.
*
* @return array Array of Requirement instances
*/
public function getRequirements()
{
$array = array();
foreach ($this->requirements as $req) {
if (!$req->isOptional()) {
$array[] = $req;
}
}
return $array;
}
/**
* Returns the mandatory requirements that were not met.
*
* @return array Array of Requirement instances
*/
public function getFailedRequirements()
{
$array = array();
foreach ($this->requirements as $req) {
if (!$req->isFulfilled() && !$req->isOptional()) {
$array[] = $req;
}
}
return $array;
}
/**
* Returns all optional recommendations.
*
* @return array Array of Requirement instances
*/
public function getRecommendations()
{
$array = array();
foreach ($this->requirements as $req) {
if ($req->isOptional()) {
$array[] = $req;
}
}
return $array;
}
/**
* Returns the recommendations that were not met.
*
* @return array Array of Requirement instances
*/
public function getFailedRecommendations()
{
$array = array();
foreach ($this->requirements as $req) {
if (!$req->isFulfilled() && $req->isOptional()) {
$array[] = $req;
}
}
return $array;
}
/**
* Returns whether a php.ini configuration is not correct.
*
* @return bool php.ini configuration problem?
*/
public function hasPhpIniConfigIssue()
{
foreach ($this->requirements as $req) {
if (!$req->isFulfilled() && $req instanceof PhpIniRequirement) {
return true;
}
}
return false;
}
/**
* Returns the PHP configuration file (php.ini) path.
*
* @return string|false php.ini file path
*/
public function getPhpIniConfigPath()
{
return get_cfg_var('cfg_file_path');
}
}
/**
* This class specifies all requirements and optional recommendations that
* are necessary to run the Symfony Standard Edition.
*
* @author Tobias Schultze <http://tobion.de>
* @author Fabien Potencier <fabien@symfony.com>
*/
class SymfonyRequirements extends RequirementCollection
{
const REQUIRED_PHP_VERSION = '5.3.3';
/**
* Constructor that initializes the requirements.
*/
public function __construct()
{
/* mandatory requirements follow */
$installedPhpVersion = phpversion();
$this->addRequirement(
version_compare($installedPhpVersion, self::REQUIRED_PHP_VERSION, '>='),
sprintf('PHP version must be at least %s (%s installed)', self::REQUIRED_PHP_VERSION, $installedPhpVersion),
sprintf('You are running PHP version "<strong>%s</strong>", but Symfony needs at least PHP "<strong>%s</strong>" to run.
Before using Symfony, upgrade your PHP installation, preferably to the latest version.',
$installedPhpVersion, self::REQUIRED_PHP_VERSION),
sprintf('Install PHP %s or newer (installed version is %s)', self::REQUIRED_PHP_VERSION, $installedPhpVersion)
);
$this->addRequirement(
version_compare($installedPhpVersion, '5.3.16', '!='),
'PHP version must not be 5.3.16 as Symfony won\'t work properly with it',
'Install PHP 5.3.17 or newer (or downgrade to an earlier PHP version)'
);
$this->addRequirement(
is_dir(__DIR__.'/../vendor/composer'),
'Vendor libraries must be installed',
'Vendor libraries are missing. Install composer following instructions from <a href="http://getcomposer.org/">http://getcomposer.org/</a>. '.
'Then run "<strong>php composer.phar install</strong>" to install them.'
);
$cacheDir = is_dir(__DIR__.'/../var/cache') ? __DIR__.'/../var/cache' : __DIR__.'/cache';
$this->addRequirement(
is_writable($cacheDir),
'app/cache/ or var/cache/ directory must be writable',
'Change the permissions of either "<strong>app/cache/</strong>" or "<strong>var/cache/</strong>" directory so that the web server can write into it.'
);
$logsDir = is_dir(__DIR__.'/../var/logs') ? __DIR__.'/../var/logs' : __DIR__.'/logs';
$this->addRequirement(
is_writable($logsDir),
'app/logs/ or var/logs/ directory must be writable',
'Change the permissions of either "<strong>app/logs/</strong>" or "<strong>var/logs/</strong>" directory so that the web server can write into it.'
);
if (version_compare($installedPhpVersion, '7.0.0', '<')) {
$this->addPhpIniRequirement(
'date.timezone', true, false,
'date.timezone setting must be set',
'Set the "<strong>date.timezone</strong>" setting in php.ini<a href="#phpini">*</a> (like Europe/Paris).'
);
}
if (version_compare($installedPhpVersion, self::REQUIRED_PHP_VERSION, '>=')) {
$timezones = array();
foreach (DateTimeZone::listAbbreviations() as $abbreviations) {
foreach ($abbreviations as $abbreviation) {
$timezones[$abbreviation['timezone_id']] = true;
}
}
$this->addRequirement(
isset($timezones[@date_default_timezone_get()]),
sprintf('Configured default timezone "%s" must be supported by your installation of PHP', @date_default_timezone_get()),
'Your default timezone is not supported by PHP. Check for typos in your <strong>php.ini</strong> file and have a look at the list of deprecated timezones at <a href="http://php.net/manual/en/timezones.others.php">http://php.net/manual/en/timezones.others.php</a>.'
);
}
$this->addRequirement(
function_exists('iconv'),
'iconv() must be available',
'Install and enable the <strong>iconv</strong> extension.'
);
$this->addRequirement(
function_exists('json_encode'),
'json_encode() must be available',
'Install and enable the <strong>JSON</strong> extension.'
);
$this->addRequirement(
function_exists('session_start'),
'session_start() must be available',
'Install and enable the <strong>session</strong> extension.'
);
$this->addRequirement(
function_exists('ctype_alpha'),
'ctype_alpha() must be available',
'Install and enable the <strong>ctype</strong> extension.'
);
$this->addRequirement(
function_exists('token_get_all'),
'token_get_all() must be available',
'Install and enable the <strong>Tokenizer</strong> extension.'
);
$this->addRequirement(
function_exists('simplexml_import_dom'),
'simplexml_import_dom() must be available',
'Install and enable the <strong>SimpleXML</strong> extension.'
);
if (function_exists('apc_store') && ini_get('apc.enabled')) {
if (version_compare($installedPhpVersion, '5.4.0', '>=')) {
$this->addRequirement(
version_compare(phpversion('apc'), '3.1.13', '>='),
'APC version must be at least 3.1.13 when using PHP 5.4',
'Upgrade your <strong>APC</strong> extension (3.1.13+).'
);
} else {
$this->addRequirement(
version_compare(phpversion('apc'), '3.0.17', '>='),
'APC version must be at least 3.0.17',
'Upgrade your <strong>APC</strong> extension (3.0.17+).'
);
}
}
$this->addPhpIniRequirement('detect_unicode', false);
if (extension_loaded('suhosin')) {
$this->addPhpIniRequirement(
'suhosin.executor.include.whitelist',
create_function('$cfgValue', 'return false !== stripos($cfgValue, "phar");'),
false,
'suhosin.executor.include.whitelist must be configured correctly in php.ini',
'Add "<strong>phar</strong>" to <strong>suhosin.executor.include.whitelist</strong> in php.ini<a href="#phpini">*</a>.'
);
}
if (extension_loaded('xdebug')) {
$this->addPhpIniRequirement(
'xdebug.show_exception_trace', false, true
);
$this->addPhpIniRequirement(
'xdebug.scream', false, true
);
$this->addPhpIniRecommendation(
'xdebug.max_nesting_level',
create_function('$cfgValue', 'return $cfgValue > 100;'),
true,
'xdebug.max_nesting_level should be above 100 in php.ini',
'Set "<strong>xdebug.max_nesting_level</strong>" to e.g. "<strong>250</strong>" in php.ini<a href="#phpini">*</a> to stop Xdebug\'s infinite recursion protection erroneously throwing a fatal error in your project.'
);
}
$pcreVersion = defined('PCRE_VERSION') ? (float) PCRE_VERSION : null;
$this->addRequirement(
null !== $pcreVersion,
'PCRE extension must be available',
'Install the <strong>PCRE</strong> extension (version 8.0+).'
);
if (extension_loaded('mbstring')) {
$this->addPhpIniRequirement(
'mbstring.func_overload',
create_function('$cfgValue', 'return (int) $cfgValue === 0;'),
true,
'string functions should not be overloaded',
'Set "<strong>mbstring.func_overload</strong>" to <strong>0</strong> in php.ini<a href="#phpini">*</a> to disable function overloading by the mbstring extension.'
);
}
/* optional recommendations follow */
if (file_exists(__DIR__.'/../vendor/composer')) {
require_once __DIR__.'/../vendor/autoload.php';
try {
$r = new ReflectionClass('Sensio\Bundle\DistributionBundle\SensioDistributionBundle');
$contents = file_get_contents(dirname($r->getFileName()).'/Resources/skeleton/app/SymfonyRequirements.php');
} catch (ReflectionException $e) {
$contents = '';
}
$this->addRecommendation(
file_get_contents(__FILE__) === $contents,
'Requirements file should be up-to-date',
'Your requirements file is outdated. Run composer install and re-check your configuration.'
);
}
$this->addRecommendation(
version_compare($installedPhpVersion, '5.3.4', '>='),
'You should use at least PHP 5.3.4 due to PHP bug #52083 in earlier versions',
'Your project might malfunction randomly due to PHP bug #52083 ("Notice: Trying to get property of non-object"). Install PHP 5.3.4 or newer.'
);
$this->addRecommendation(
version_compare($installedPhpVersion, '5.3.8', '>='),
'When using annotations you should have at least PHP 5.3.8 due to PHP bug #55156',
'Install PHP 5.3.8 or newer if your project uses annotations.'
);
$this->addRecommendation(
version_compare($installedPhpVersion, '5.4.0', '!='),
'You should not use PHP 5.4.0 due to the PHP bug #61453',
'Your project might not work properly due to the PHP bug #61453 ("Cannot dump definitions which have method calls"). Install PHP 5.4.1 or newer.'
);
$this->addRecommendation(
version_compare($installedPhpVersion, '5.4.11', '>='),
'When using the logout handler from the Symfony Security Component, you should have at least PHP 5.4.11 due to PHP bug #63379 (as a workaround, you can also set invalidate_session to false in the security logout handler configuration)',
'Install PHP 5.4.11 or newer if your project uses the logout handler from the Symfony Security Component.'
);
$this->addRecommendation(
(version_compare($installedPhpVersion, '5.3.18', '>=') && version_compare($installedPhpVersion, '5.4.0', '<'))
||
version_compare($installedPhpVersion, '5.4.8', '>='),
'You should use PHP 5.3.18+ or PHP 5.4.8+ to always get nice error messages for fatal errors in the development environment due to PHP bug #61767/#60909',
'Install PHP 5.3.18+ or PHP 5.4.8+ if you want nice error messages for all fatal errors in the development environment.'
);
if (null !== $pcreVersion) {
$this->addRecommendation(
$pcreVersion >= 8.0,
sprintf('PCRE extension should be at least version 8.0 (%s installed)', $pcreVersion),
'<strong>PCRE 8.0+</strong> is preconfigured in PHP since 5.3.2 but you are using an outdated version of it. Symfony probably works anyway but it is recommended to upgrade your PCRE extension.'
);
}
$this->addRecommendation(
class_exists('DomDocument'),
'PHP-DOM and PHP-XML modules should be installed',
'Install and enable the <strong>PHP-DOM</strong> and the <strong>PHP-XML</strong> modules.'
);
$this->addRecommendation(
function_exists('mb_strlen'),
'mb_strlen() should be available',
'Install and enable the <strong>mbstring</strong> extension.'
);
$this->addRecommendation(
function_exists('iconv'),
'iconv() should be available',
'Install and enable the <strong>iconv</strong> extension.'
);
$this->addRecommendation(
function_exists('utf8_decode'),
'utf8_decode() should be available',
'Install and enable the <strong>XML</strong> extension.'
);
$this->addRecommendation(
function_exists('filter_var'),
'filter_var() should be available',
'Install and enable the <strong>filter</strong> extension.'
);
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->addRecommendation(
function_exists('posix_isatty'),
'posix_isatty() should be available',
'Install and enable the <strong>php_posix</strong> extension (used to colorize the CLI output).'
);
}
$this->addRecommendation(
extension_loaded('intl'),
'intl extension should be available',
'Install and enable the <strong>intl</strong> extension (used for validators).'
);
if (extension_loaded('intl')) {
// in some WAMP server installations, new Collator() returns null
$this->addRecommendation(
null !== new Collator('fr_FR'),
'intl extension should be correctly configured',
'The intl extension does not behave properly. This problem is typical on PHP 5.3.X x64 WIN builds.'
);
// check for compatible ICU versions (only done when you have the intl extension)
if (defined('INTL_ICU_VERSION')) {
$version = INTL_ICU_VERSION;
} else {
$reflector = new ReflectionExtension('intl');
ob_start();
$reflector->info();
$output = strip_tags(ob_get_clean());
preg_match('/^ICU version +(?:=> )?(.*)$/m', $output, $matches);
$version = $matches[1];
}
$this->addRecommendation(
version_compare($version, '4.0', '>='),
'intl ICU version should be at least 4+',
'Upgrade your <strong>intl</strong> extension with a newer ICU version (4+).'
);
if (class_exists('Symfony\Component\Intl\Intl')) {
$this->addRecommendation(
\Symfony\Component\Intl\Intl::getIcuDataVersion() === \Symfony\Component\Intl\Intl::getIcuVersion(),
sprintf('intl ICU version installed on your system (%s) should match the ICU data bundled with Symfony (%s)', \Symfony\Component\Intl\Intl::getIcuVersion(), \Symfony\Component\Intl\Intl::getIcuDataVersion()),
'In most cases you should be fine, but please verify there is no inconsistencies between data provided by Symfony and the intl extension. See https://github.com/symfony/symfony/issues/15007 for an example of inconsistencies you might run into.'
);
}
$this->addPhpIniRecommendation(
'intl.error_level',
create_function('$cfgValue', 'return (int) $cfgValue === 0;'),
true,
'intl.error_level should be 0 in php.ini',
'Set "<strong>intl.error_level</strong>" to "<strong>0</strong>" in php.ini<a href="#phpini">*</a> to inhibit the messages when an error occurs in ICU functions.'
);
}
$accelerator =
(extension_loaded('eaccelerator') && ini_get('eaccelerator.enable'))
||
(extension_loaded('apc') && ini_get('apc.enabled'))
||
(extension_loaded('Zend Optimizer+') && ini_get('zend_optimizerplus.enable'))
||
(extension_loaded('Zend OPcache') && ini_get('opcache.enable'))
||
(extension_loaded('xcache') && ini_get('xcache.cacher'))
||
(extension_loaded('wincache') && ini_get('wincache.ocenabled'))
;
$this->addRecommendation(
$accelerator,
'a PHP accelerator should be installed',
'Install and/or enable a <strong>PHP accelerator</strong> (highly recommended).'
);
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$this->addRecommendation(
$this->getRealpathCacheSize() > 1000,
'realpath_cache_size should be above 1024 in php.ini',
'Set "<strong>realpath_cache_size</strong>" to e.g. "<strong>1024</strong>" in php.ini<a href="#phpini">*</a> to improve performance on windows.'
);
}
$this->addPhpIniRecommendation('short_open_tag', false);
$this->addPhpIniRecommendation('magic_quotes_gpc', false, true);
$this->addPhpIniRecommendation('register_globals', false, true);
$this->addPhpIniRecommendation('session.auto_start', false);
$this->addRecommendation(
class_exists('PDO'),
'PDO should be installed',
'Install <strong>PDO</strong> (mandatory for Doctrine).'
);
if (class_exists('PDO')) {
$drivers = PDO::getAvailableDrivers();
$this->addRecommendation(
count($drivers) > 0,
sprintf('PDO should have some drivers installed (currently available: %s)', count($drivers) ? implode(', ', $drivers) : 'none'),
'Install <strong>PDO drivers</strong> (mandatory for Doctrine).'
);
}
}
/**
* Loads realpath_cache_size from php.ini and converts it to int.
*
* (e.g. 16k is converted to 16384 int)
*
* @return int
*/
protected function getRealpathCacheSize()
{
$size = ini_get('realpath_cache_size');
$size = trim($size);
$unit = strtolower(substr($size, -1, 1));
switch ($unit) {
case 'g':
return $size * 1024 * 1024 * 1024;
case 'm':
return $size * 1024 * 1024;
case 'k':
return $size * 1024;
default:
return (int) $size;
}
}
}

View File

@@ -0,0 +1,142 @@
<?php
require_once dirname(__FILE__).'/SymfonyRequirements.php';
$lineSize = 70;
$symfonyRequirements = new SymfonyRequirements();
$iniPath = $symfonyRequirements->getPhpIniConfigPath();
echo_title('Symfony Requirements Checker');
echo '> PHP is using the following php.ini file:'.PHP_EOL;
if ($iniPath) {
echo_style('green', ' '.$iniPath);
} else {
echo_style('yellow', ' WARNING: No configuration file (php.ini) used by PHP!');
}
echo PHP_EOL.PHP_EOL;
echo '> Checking Symfony requirements:'.PHP_EOL.' ';
$messages = array();
foreach ($symfonyRequirements->getRequirements() as $req) {
/** @var $req Requirement */
if ($helpText = get_error_message($req, $lineSize)) {
echo_style('red', 'E');
$messages['error'][] = $helpText;
} else {
echo_style('green', '.');
}
}
$checkPassed = empty($messages['error']);
foreach ($symfonyRequirements->getRecommendations() as $req) {
if ($helpText = get_error_message($req, $lineSize)) {
echo_style('yellow', 'W');
$messages['warning'][] = $helpText;
} else {
echo_style('green', '.');
}
}
if ($checkPassed) {
echo_block('success', 'OK', 'Your system is ready to run Symfony projects');
} else {
echo_block('error', 'ERROR', 'Your system is not ready to run Symfony projects');
echo_title('Fix the following mandatory requirements', 'red');
foreach ($messages['error'] as $helpText) {
echo ' * '.$helpText.PHP_EOL;
}
}
if (!empty($messages['warning'])) {
echo_title('Optional recommendations to improve your setup', 'yellow');
foreach ($messages['warning'] as $helpText) {
echo ' * '.$helpText.PHP_EOL;
}
}
echo PHP_EOL;
echo_style('title', 'Note');
echo ' The command console could use a different php.ini file'.PHP_EOL;
echo_style('title', '~~~~');
echo ' than the one used with your web server. To be on the'.PHP_EOL;
echo ' safe side, please check the requirements from your web'.PHP_EOL;
echo ' server using the ';
echo_style('yellow', 'web/config.php');
echo ' script.'.PHP_EOL;
echo PHP_EOL;
exit($checkPassed ? 0 : 1);
function get_error_message(Requirement $requirement, $lineSize)
{
if ($requirement->isFulfilled()) {
return;
}
$errorMessage = wordwrap($requirement->getTestMessage(), $lineSize - 3, PHP_EOL.' ').PHP_EOL;
$errorMessage .= ' > '.wordwrap($requirement->getHelpText(), $lineSize - 5, PHP_EOL.' > ').PHP_EOL;
return $errorMessage;
}
function echo_title($title, $style = null)
{
$style = $style ?: 'title';
echo PHP_EOL;
echo_style($style, $title.PHP_EOL);
echo_style($style, str_repeat('~', strlen($title)).PHP_EOL);
echo PHP_EOL;
}
function echo_style($style, $message)
{
// ANSI color codes
$styles = array(
'reset' => "\033[0m",
'red' => "\033[31m",
'green' => "\033[32m",
'yellow' => "\033[33m",
'error' => "\033[37;41m",
'success' => "\033[37;42m",
'title' => "\033[34m",
);
$supports = has_color_support();
echo($supports ? $styles[$style] : '').$message.($supports ? $styles['reset'] : '');
}
function echo_block($style, $title, $message)
{
$message = ' '.trim($message).' ';
$width = strlen($message);
echo PHP_EOL.PHP_EOL;
echo_style($style, str_repeat(' ', $width).PHP_EOL);
echo_style($style, str_pad(' ['.$title.']', $width, ' ', STR_PAD_RIGHT).PHP_EOL);
echo_style($style, str_pad($message, $width, ' ', STR_PAD_RIGHT).PHP_EOL);
echo_style($style, str_repeat(' ', $width).PHP_EOL);
}
function has_color_support()
{
static $support;
if (null === $support) {
if (DIRECTORY_SEPARATOR == '\\') {
$support = false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI');
} else {
$support = function_exists('posix_isatty') && @posix_isatty(STDOUT);
}
}
return $support;
}

View File

@@ -0,0 +1,213 @@
<?php
/*
* ************** CAUTION **************
*
* DO NOT EDIT THIS FILE as it will be overridden by Composer as part of
* the installation/update process. The original file resides in the
* SensioDistributionBundle.
*
* ************** CAUTION **************
*/
if (!isset($_SERVER['HTTP_HOST'])) {
exit('This script cannot be run from the CLI. Run it from a browser.');
}
if (!in_array(@$_SERVER['REMOTE_ADDR'], array(
'127.0.0.1',
'::1',
))) {
header('HTTP/1.0 403 Forbidden');
exit('This script is only accessible from localhost.');
}
require_once dirname(__FILE__).'/../app/SymfonyRequirements.php';
$symfonyRequirements = new SymfonyRequirements();
$majorProblems = $symfonyRequirements->getFailedRequirements();
$minorProblems = $symfonyRequirements->getFailedRecommendations();
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="robots" content="noindex,nofollow" />
<title>Symfony Configuration Checker</title>
<link rel="stylesheet" href="bundles/framework/css/structure.css" media="all" />
<link rel="stylesheet" href="bundles/framework/css/body.css" media="all" />
<style type="text/css">
/* styles copied from bundles/sensiodistribution/webconfigurator/css/install.css */
body {
font-size: 14px;
font-family: "Lucida Sans Unicode", "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
}
.sf-reset h1.title {
font-size: 45px;
padding-bottom: 30px;
}
.sf-reset h2 {
font-weight: bold;
color: #FFFFFF;
/* Font is reset to sans-serif (like body) */
font-family: "Lucida Sans Unicode", "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
margin-bottom: 10px;
background-color: #aacd4e;
padding: 2px 4px;
display: inline-block;
text-transform: uppercase;
}
.sf-reset ul a,
.sf-reset ul a:hover {
background: url(../images/blue-arrow.png) no-repeat right 6px;
padding-right: 10px;
}
.sf-reset ul, ol {
padding-left: 20px;
}
.sf-reset li {
padding-bottom: 18px;
}
.sf-reset ol li {
list-style-type: decimal;
}
.sf-reset ul li {
list-style-type: none;
}
.sf-reset .symfony-blocks-install {
overflow: hidden;
}
.sf-reset .symfony-install-continue {
font-size: 0.95em;
padding-left: 0;
}
.sf-reset .symfony-install-continue li {
padding-bottom: 10px;
}
.sf-reset .ok {
color: #fff;
font-family: "Lucida Sans Unicode", "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
background-color: #6d6;
padding: 10px;
margin-bottom: 20px;
}
.sf-reset .ko {
background-color: #d66;
}
.sf-reset p.help {
padding: 12px 16px;
word-break: break-word;
}
.version {
text-align: right;
font-size: 10px;
margin-right: 20px;
}
.sf-reset a,
.sf-reset li a {
color: #08C;
text-decoration: none;
}
.sf-reset a:hover,
.sf-reset li a:hover {
color: #08C;
text-decoration: underline;
}
.sf-reset textarea {
padding: 7px;
}
</style>
</head>
<body>
<div id="content">
<div class="header clear-fix">
<div class="header-logo">
<img src="bundles/framework/images/logo_symfony.png" alt="Symfony" />
</div>
<div class="search">
<form method="get" action="http://symfony.com/search">
<div class="form-row">
<label for="search-id">
<img src="bundles/framework/images/grey_magnifier.png" alt="Search on Symfony website" />
</label>
<input name="q" id="search-id" type="search" placeholder="Search on Symfony website" />
<button type="submit" class="sf-button">
<span class="border-l">
<span class="border-r">
<span class="btn-bg">OK</span>
</span>
</span>
</button>
</div>
</form>
</div>
</div>
<div class="sf-reset">
<div class="block">
<div class="symfony-block-content">
<h1 class="title">Configuration Checker</h1>
<p>
This script analyzes your system to check whether is
ready to run Symfony applications.
</p>
<?php if (count($majorProblems)): ?>
<h2 class="ko">Major problems</h2>
<p>Major problems have been detected and <strong>must</strong> be fixed before continuing:</p>
<ol>
<?php foreach ($majorProblems as $problem): ?>
<li><?php echo $problem->getTestMessage() ?>
<p class="help"><em><?php echo $problem->getHelpHtml() ?></em></p>
</li>
<?php endforeach; ?>
</ol>
<?php endif; ?>
<?php if (count($minorProblems)): ?>
<h2>Recommendations</h2>
<p>
<?php if (count($majorProblems)): ?>Additionally, to<?php else: ?>To<?php endif; ?> enhance your Symfony experience,
its recommended that you fix the following:
</p>
<ol>
<?php foreach ($minorProblems as $problem): ?>
<li><?php echo $problem->getTestMessage() ?>
<p class="help"><em><?php echo $problem->getHelpHtml() ?></em></p>
</li>
<?php endforeach; ?>
</ol>
<?php endif; ?>
<?php if ($symfonyRequirements->hasPhpIniConfigIssue()): ?>
<p id="phpini">*
<?php if ($symfonyRequirements->getPhpIniConfigPath()): ?>
Changes to the <strong>php.ini</strong> file must be done in "<strong><?php echo $symfonyRequirements->getPhpIniConfigPath() ?></strong>".
<?php else: ?>
To change settings, create a "<strong>php.ini</strong>".
<?php endif; ?>
</p>
<?php endif; ?>
<?php if (!count($majorProblems) && !count($minorProblems)): ?>
<p class="ok">All checks passed successfully. Your system is ready to run Symfony applications.</p>
<?php endif; ?>
<ul class="symfony-install-continue">
<?php if (count($majorProblems) || count($minorProblems)): ?>
<li><a href="config.php">Re-check configuration</a></li>
<?php endif; ?>
</ul>
</div>
</div>
</div>
<div class="version">Symfony Standard Edition</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\DistributionBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* SensioDistributionBundle.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Marc Weistroff <marc.weistroff@sensio.com>
* @author Jérôme Vieilledent <lolautruche@gmail.com>
*/
class SensioDistributionBundle extends Bundle
{
}

View File

@@ -0,0 +1,14 @@
UPGRADE FROM 4.0 to 5.0
=======================
* The web configurator got removed. So you need to remove the `_configurator`
routing entry from `app/config/routing_dev.yml`.
* The generated `app/bootstrap.php.cache` does not include autoloading anymore.
So you need to add the autoloading code in your front controllers `web/app.php`,
`web/app_dev.php`, `app/console` and `app/phpunit.xml.dist` (bootstrap config).
* If you have been using the Symfony 3 directory structure already, you need to
overwrite the cache and log directories in your `AppKernel` as it is also done
in Symfony 3 now (see
[`app/AppKernel.php`](https://github.com/symfony/symfony-standard/blob/master/app/AppKernel.php#L31-L44)).

View File

@@ -0,0 +1,32 @@
{
"name": "sensio/distribution-bundle",
"description": "Base bundle for Symfony Distributions",
"keywords": ["distribution","configuration"],
"type": "symfony-bundle",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
}
],
"require": {
"php": ">=5.3.9",
"symfony/dependency-injection": "~2.3|~3.0",
"symfony/config": "~2.3|~3.0",
"symfony/filesystem": "~2.3|~3.0",
"symfony/http-kernel": "~2.3|~3.0",
"symfony/class-loader": "~2.3|~3.0",
"symfony/process": "~2.3|~3.0",
"sensiolabs/security-checker": "~3.0"
},
"autoload": {
"psr-4": { "Sensio\\Bundle\\DistributionBundle\\": "" }
},
"extra": {
"branch-alias": {
"dev-master": "5.0.x-dev"
}
},
"minimum-stability": "dev"
}

View File

@@ -0,0 +1,33 @@
language: php
sudo: false
cache:
directories:
- $HOME/.composer/cache
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
matrix:
include:
- php: 5.3
env: deps=low
fast_finish: true
env:
global:
- deps=no
before_install:
- travis_retry composer self-update
install:
- if [[ "$TRAVIS_PHP_VERSION" != "5.3" ]] && [[ "$TRAVIS_PHP_VERSION" != "5.4" ]]; then composer require --no-update symfony/psr-http-message-bridge zendframework/zend-diactoros; fi;
- if [ "$deps" = "no" ]; then composer --prefer-source install; fi;
- if [ "$deps" = "low" ]; then composer --prefer-source --prefer-lowest --prefer-stable update; fi;

View File

@@ -0,0 +1,248 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
/**
* The Cache class handles the Cache annotation parts.
*
* @author Fabien Potencier <fabien@symfony.com>
* @Annotation
*/
class Cache extends ConfigurationAnnotation
{
/**
* The expiration date as a valid date for the strtotime() function.
*
* @var string
*/
protected $expires;
/**
* The number of seconds that the response is considered fresh by a private
* cache like a web browser.
*
* @var int
*/
protected $maxage;
/**
* The number of seconds that the response is considered fresh by a public
* cache like a reverse proxy cache.
*
* @var int
*/
protected $smaxage;
/**
* Whether the response is public or not.
*
* @var bool
*/
protected $public;
/**
* Additional "Vary:"-headers.
*
* @var array
*/
protected $vary;
/**
* An expression to compute the Last-Modified HTTP header.
*
* @var string
*/
protected $lastModified;
/**
* An expression to compute the ETag HTTP header.
*
* @var string
*/
protected $etag;
/**
* Returns the expiration date for the Expires header field.
*
* @return string
*/
public function getExpires()
{
return $this->expires;
}
/**
* Sets the expiration date for the Expires header field.
*
* @param string $expires A valid php date
*/
public function setExpires($expires)
{
$this->expires = $expires;
}
/**
* Sets the number of seconds for the max-age cache-control header field.
*
* @param int $maxage A number of seconds
*/
public function setMaxAge($maxage)
{
$this->maxage = $maxage;
}
/**
* Returns the number of seconds the response is considered fresh by a
* private cache.
*
* @return int
*/
public function getMaxAge()
{
return $this->maxage;
}
/**
* Sets the number of seconds for the s-maxage cache-control header field.
*
* @param int $smaxage A number of seconds
*/
public function setSMaxAge($smaxage)
{
$this->smaxage = $smaxage;
}
/**
* Returns the number of seconds the response is considered fresh by a
* public cache.
*
* @return int
*/
public function getSMaxAge()
{
return $this->smaxage;
}
/**
* Returns whether or not a response is public.
*
* @return bool
*/
public function isPublic()
{
return $this->public === true;
}
/**
* Returns whether or not a response is private.
*
* @return bool
*/
public function isPrivate()
{
return $this->public === false;
}
/**
* Sets a response public.
*
* @param bool $public A boolean value
*/
public function setPublic($public)
{
$this->public = (bool) $public;
}
/**
* Returns the custom "Vary"-headers.
*
* @return array
*/
public function getVary()
{
return $this->vary;
}
/**
* Add additional "Vary:"-headers.
*
* @param array $vary
*/
public function setVary($vary)
{
$this->vary = $vary;
}
/**
* Sets the "Last-Modified"-header expression.
*
* @param string $expression
*/
public function setLastModified($expression)
{
$this->lastModified = $expression;
}
/**
* Returns the "Last-Modified"-header expression.
*
* @return string
*/
public function getLastModified()
{
return $this->lastModified;
}
/**
* Sets the "ETag"-header expression.
*
* @param string $expression
*/
public function setETag($expression)
{
$this->etag = $expression;
}
/**
* Returns the "ETag"-header expression.
*
* @return string
*/
public function getETag()
{
return $this->etag;
}
/**
* Returns the annotation alias name.
*
* @return string
*
* @see ConfigurationInterface
*/
public function getAliasName()
{
return 'cache';
}
/**
* Only one cache directive is allowed.
*
* @return bool
*
* @see ConfigurationInterface
*/
public function allowArray()
{
return false;
}
}

View File

@@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
/**
* Base configuration annotation.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
abstract class ConfigurationAnnotation implements ConfigurationInterface
{
public function __construct(array $values)
{
foreach ($values as $k => $v) {
if (!method_exists($this, $name = 'set'.$k)) {
throw new \RuntimeException(sprintf('Unknown key "%s" for annotation "@%s".', $k, get_class($this)));
}
$this->$name($v);
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
/**
* ConfigurationInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ConfigurationInterface
{
/**
* Returns the alias name for an annotated configuration.
*
* @return string
*/
public function getAliasName();
/**
* Returns whether multiple annotations of this type are allowed.
*
* @return bool
*/
public function allowArray();
}

View File

@@ -0,0 +1,82 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
/**
* The Method class handles the Method annotation parts.
*
* @author Fabien Potencier <fabien@symfony.com>
* @Annotation
*/
class Method extends ConfigurationAnnotation
{
/**
* An array of restricted HTTP methods.
*
* @var array
*/
protected $methods = array();
/**
* Returns the array of HTTP methods.
*
* @return array
*/
public function getMethods()
{
return $this->methods;
}
/**
* Sets the HTTP methods.
*
* @param array|string $methods An HTTP method or an array of HTTP methods
*/
public function setMethods($methods)
{
$this->methods = is_array($methods) ? $methods : array($methods);
}
/**
* Sets the HTTP methods.
*
* @param array|string $methods An HTTP method or an array of HTTP methods
*/
public function setValue($methods)
{
$this->setMethods($methods);
}
/**
* Returns the annotation alias name.
*
* @return string
*
* @see ConfigurationInterface
*/
public function getAliasName()
{
return 'method';
}
/**
* Only one method directive is allowed.
*
* @return bool
*
* @see ConfigurationInterface
*/
public function allowArray()
{
return false;
}
}

View File

@@ -0,0 +1,190 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
/**
* The ParamConverter class handles the ParamConverter annotation parts.
*
* @author Fabien Potencier <fabien@symfony.com>
* @Annotation
*/
class ParamConverter extends ConfigurationAnnotation
{
/**
* The parameter name.
*
* @var string
*/
protected $name;
/**
* The parameter class.
*
* @var string
*/
protected $class;
/**
* An array of options.
*
* @var array
*/
protected $options = array();
/**
* Whether or not the parameter is optional.
*
* @var bool
*/
protected $optional = false;
/**
* Use explicitly named converter instead of iterating by priorities.
*
* @var string
*/
protected $converter;
/**
* Returns the parameter name.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Sets the parameter name.
*
* @param string $name The parameter name
*/
public function setValue($name)
{
$this->setName($name);
}
/**
* Sets the parameter name.
*
* @param string $name The parameter name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Returns the parameter class name.
*
* @return string $name
*/
public function getClass()
{
return $this->class;
}
/**
* Sets the parameter class name.
*
* @param string $class The parameter class name
*/
public function setClass($class)
{
$this->class = $class;
}
/**
* Returns an array of options.
*
* @return array
*/
public function getOptions()
{
return $this->options;
}
/**
* Sets an array of options.
*
* @param array $options An array of options
*/
public function setOptions($options)
{
$this->options = $options;
}
/**
* Sets whether or not the parameter is optional.
*
* @param bool $optional Whether the parameter is optional
*/
public function setIsOptional($optional)
{
$this->optional = (bool) $optional;
}
/**
* Returns whether or not the parameter is optional.
*
* @return bool
*/
public function isOptional()
{
return $this->optional;
}
/**
* Get explicit converter name.
*
* @return string
*/
public function getConverter()
{
return $this->converter;
}
/**
* Set explicit converter name.
*
* @param string $converter
*/
public function setConverter($converter)
{
$this->converter = $converter;
}
/**
* Returns the annotation alias name.
*
* @return string
*
* @see ConfigurationInterface
*/
public function getAliasName()
{
return 'converters';
}
/**
* Multiple ParamConverters are allowed.
*
* @return bool
*
* @see ConfigurationInterface
*/
public function allowArray()
{
return true;
}
}

View File

@@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
use Symfony\Component\Routing\Annotation\Route as BaseRoute;
/**
* @author Kris Wallsmith <kris@symfony.com>
* @Annotation
*/
class Route extends BaseRoute
{
protected $service;
public function setService($service)
{
// avoid a BC notice in case of @Route(service="") with sf ^2.7
if (null === $this->getPath()) {
$this->setPath('');
}
$this->service = $service;
}
public function getService()
{
return $this->service;
}
/**
* Multiple route annotations are allowed.
*
* @return bool
*
* @see ConfigurationInterface
*/
public function allowArray()
{
return true;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
/**
* The Security class handles the Security annotation.
*
* @author Fabien Potencier <fabien@symfony.com>
* @Annotation
*/
class Security extends ConfigurationAnnotation
{
protected $expression;
public function getExpression()
{
return $this->expression;
}
public function setExpression($expression)
{
$this->expression = $expression;
}
public function setValue($expression)
{
$this->setExpression($expression);
}
public function getAliasName()
{
return 'security';
}
public function allowArray()
{
return false;
}
}

View File

@@ -0,0 +1,186 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
/**
* The Template class handles the Template annotation parts.
*
* @author Fabien Potencier <fabien@symfony.com>
* @Annotation
*/
class Template extends ConfigurationAnnotation
{
/**
* The template reference.
*
* @var TemplateReference|string
*/
protected $template;
/**
* The template engine used when a specific template isn't specified.
*
* @var string
*/
protected $engine = 'twig';
/**
* The associative array of template variables.
*
* @var array
*/
protected $vars = array();
/**
* Should the template be streamed?
*
* @var bool
*/
protected $streamable = false;
/**
* The controller (+action) this annotation is set to.
*
* @var array
*/
private $owner;
/**
* Returns the array of templates variables.
*
* @return array
*/
public function getVars()
{
return $this->vars;
}
/**
* @param bool $streamable
*/
public function setIsStreamable($streamable)
{
$this->streamable = $streamable;
}
/**
* @return bool
*/
public function isStreamable()
{
return (bool) $this->streamable;
}
/**
* Sets the template variables.
*
* @param array $vars The template variables
*/
public function setVars($vars)
{
$this->vars = $vars;
}
/**
* Returns the engine used when guessing template names.
*
* @return string
*/
public function getEngine()
{
return $this->engine;
}
/**
* Sets the engine used when guessing template names.
*
* @param string
*/
public function setEngine($engine)
{
$this->engine = $engine;
}
/**
* Sets the template logic name.
*
* @param string $template The template logic name
*/
public function setValue($template)
{
$this->setTemplate($template);
}
/**
* Returns the template reference.
*
* @return TemplateReference
*/
public function getTemplate()
{
return $this->template;
}
/**
* Sets the template reference.
*
* @param TemplateReference|string $template The template reference
*/
public function setTemplate($template)
{
$this->template = $template;
}
/**
* Returns the annotation alias name.
*
* @return string
*
* @see ConfigurationInterface
*/
public function getAliasName()
{
return 'template';
}
/**
* Only one template directive is allowed.
*
* @return bool
*
* @see ConfigurationInterface
*/
public function allowArray()
{
return false;
}
/**
* @param array $owner
*/
public function setOwner(array $owner)
{
$this->owner = $owner;
}
/**
* The controller (+action) this annotation is attached to.
*
* @return array
*/
public function getOwner()
{
return $this->owner;
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds tagged request.param_converter services to converter.manager service.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class AddParamConverterPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (false === $container->hasDefinition('sensio_framework_extra.converter.manager')) {
return;
}
$definition = $container->getDefinition('sensio_framework_extra.converter.manager');
foreach ($container->findTaggedServiceIds('request.param_converter') as $id => $converters) {
foreach ($converters as $converter) {
$name = isset($converter['converter']) ? $converter['converter'] : null;
$priority = isset($converter['priority']) ? $converter['priority'] : 0;
if ($priority === 'false') {
$priority = null;
}
$definition->addMethodCall('add', array(new Reference($id), $priority, $name));
}
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class LegacyPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (false === $container->hasDefinition('sensio_framework_extra.security.listener')) {
return;
}
$definition = $container->getDefinition('sensio_framework_extra.security.listener');
if ($container->hasDefinition('security.token_storage')) {
$definition->replaceArgument(0, null);
}
}
}

View File

@@ -0,0 +1,88 @@
<?php
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\NodeInterface;
/**
* FrameworkExtraBundle configuration structure.
*
* @author Henrik Bjornskov <hb@peytz.dk>
*/
class Configuration implements ConfigurationInterface
{
/**
* Generates the configuration tree.
*
* @return NodeInterface
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('sensio_framework_extra', 'array');
$rootNode
->children()
->arrayNode('router')
->addDefaultsIfNotSet()
->children()
->booleanNode('annotations')->defaultTrue()->end()
->end()
->end()
->arrayNode('request')
->addDefaultsIfNotSet()
->children()
->booleanNode('converters')->defaultTrue()->end()
->booleanNode('auto_convert')->defaultTrue()->end()
->end()
->end()
->arrayNode('view')
->addDefaultsIfNotSet()
->children()
->booleanNode('annotations')->defaultTrue()->end()
->end()
->end()
->arrayNode('cache')
->addDefaultsIfNotSet()
->children()
->booleanNode('annotations')->defaultTrue()->end()
->end()
->end()
->arrayNode('security')
->addDefaultsIfNotSet()
->children()
->booleanNode('annotations')->defaultTrue()->end()
->scalarNode('expression_language')->defaultValue('sensio_framework_extra.security.expression_language.default')->end()
->end()
->end()
->arrayNode('psr_message')
->addDefaultsIfNotSet()
->children()
->booleanNode('enabled')->defaultValue(interface_exists('Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface') && class_exists('Zend\Diactoros\ServerRequestFactory'))->end()
->end()
->end()
->arrayNode('templating')
->fixXmlConfig('controller_pattern')
->children()
->arrayNode('controller_patterns')
->prototype('scalar')
->end()
->end()
->end()
->end()
;
return $treeBuilder;
}
}

View File

@@ -0,0 +1,131 @@
<?php
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
/**
* SensioFrameworkExtraExtension.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SensioFrameworkExtraExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.xml');
if (!empty($config['templating']['controller_patterns'])) {
$container
->getDefinition('sensio_framework_extra.view.guesser')
->addArgument($config['templating']['controller_patterns']);
}
$annotationsToLoad = array();
if ($config['router']['annotations']) {
$annotationsToLoad[] = 'routing.xml';
$this->addClassesToCompile(array(
'Sensio\\Bundle\\FrameworkExtraBundle\\EventListener\\ControllerListener',
));
}
if ($config['request']['converters']) {
$annotationsToLoad[] = 'converters.xml';
$this->addClassesToCompile(array(
// cannot be added because it has some annotations
//'Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\ParamConverter',
'Sensio\\Bundle\\FrameworkExtraBundle\\EventListener\\ParamConverterListener',
'Sensio\\Bundle\\FrameworkExtraBundle\\Request\\ParamConverter\\DateTimeParamConverter',
'Sensio\\Bundle\\FrameworkExtraBundle\\Request\\ParamConverter\\DoctrineParamConverter',
'Sensio\\Bundle\\FrameworkExtraBundle\\Request\\ParamConverter\\ParamConverterInterface',
'Sensio\\Bundle\\FrameworkExtraBundle\\Request\\ParamConverter\\ParamConverterManager',
));
}
if ($config['view']['annotations']) {
$annotationsToLoad[] = 'view.xml';
$this->addClassesToCompile(array(
'Sensio\\Bundle\\FrameworkExtraBundle\\EventListener\\TemplateListener',
));
}
if ($config['cache']['annotations']) {
$annotationsToLoad[] = 'cache.xml';
$this->addClassesToCompile(array(
'Sensio\\Bundle\\FrameworkExtraBundle\\EventListener\\HttpCacheListener',
));
}
if ($config['security']['annotations']) {
$annotationsToLoad[] = 'security.xml';
if (class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage') && class_exists('Symfony\Component\Security\Core\Authorization\ExpressionLanguage')) {
$container->setAlias('sensio_framework_extra.security.expression_language', new Alias($config['security']['expression_language'], false));
} else {
$container->removeDefinition('sensio_framework_extra.security.expression_language.default');
}
$this->addClassesToCompile(array(
'Sensio\\Bundle\\FrameworkExtraBundle\\EventListener\\SecurityListener',
));
}
if ($annotationsToLoad) {
// must be first
$loader->load('annotations.xml');
foreach ($annotationsToLoad as $configFile) {
$loader->load($configFile);
}
$this->addClassesToCompile(array(
'Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\ConfigurationAnnotation',
));
if ($config['request']['converters']) {
$container->getDefinition('sensio_framework_extra.converter.listener')->replaceArgument(1, $config['request']['auto_convert']);
}
}
if ($config['psr_message']['enabled']) {
$loader->load('psr7.xml');
}
}
/**
* Returns the base path for the XSD files.
*
* @return string The XSD base path
*/
public function getXsdValidationBasePath()
{
return __DIR__.'/../Resources/config/schema';
}
public function getNamespace()
{
return 'http://symfony.com/schema/dic/symfony_extra';
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\EventListener;
/**
* CacheListener handles HTTP cache headers.
*
* It can be configured via the Cache, LastModified, and Etag annotations.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated Deprecated since 3.0, to be removed in 4.0. Use the HttpCacheListener instead.
*/
class CacheListener extends HttpCacheListener
{
}

View File

@@ -0,0 +1,113 @@
<?php
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\EventListener;
use Doctrine\Common\Annotations\Reader;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface;
use Doctrine\Common\Util\ClassUtils;
/**
* The ControllerListener class parses annotation blocks located in
* controller classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ControllerListener implements EventSubscriberInterface
{
/**
* @var Reader
*/
protected $reader;
/**
* Constructor.
*
* @param Reader $reader An Reader instance
*/
public function __construct(Reader $reader)
{
$this->reader = $reader;
}
/**
* Modifies the Request object to apply configuration information found in
* controllers annotations like the template to render or HTTP caching
* configuration.
*
* @param FilterControllerEvent $event A FilterControllerEvent instance
*/
public function onKernelController(FilterControllerEvent $event)
{
if (!is_array($controller = $event->getController())) {
return;
}
$className = class_exists('Doctrine\Common\Util\ClassUtils') ? ClassUtils::getClass($controller[0]) : get_class($controller[0]);
$object = new \ReflectionClass($className);
$method = $object->getMethod($controller[1]);
$classConfigurations = $this->getConfigurations($this->reader->getClassAnnotations($object));
$methodConfigurations = $this->getConfigurations($this->reader->getMethodAnnotations($method));
$configurations = array();
foreach (array_merge(array_keys($classConfigurations), array_keys($methodConfigurations)) as $key) {
if (!array_key_exists($key, $classConfigurations)) {
$configurations[$key] = $methodConfigurations[$key];
} elseif (!array_key_exists($key, $methodConfigurations)) {
$configurations[$key] = $classConfigurations[$key];
} else {
if (is_array($classConfigurations[$key])) {
if (!is_array($methodConfigurations[$key])) {
throw new \UnexpectedValueException('Configurations should both be an array or both not be an array');
}
$configurations[$key] = array_merge($classConfigurations[$key], $methodConfigurations[$key]);
} else {
// method configuration overrides class configuration
$configurations[$key] = $methodConfigurations[$key];
}
}
}
$request = $event->getRequest();
foreach ($configurations as $key => $attributes) {
$request->attributes->set($key, $attributes);
}
}
protected function getConfigurations(array $annotations)
{
$configurations = array();
foreach ($annotations as $configuration) {
if ($configuration instanceof ConfigurationInterface) {
if ($configuration->allowArray()) {
$configurations['_'.$configuration->getAliasName()][] = $configuration;
} elseif (!isset($configurations['_'.$configuration->getAliasName()])) {
$configurations['_'.$configuration->getAliasName()] = $configuration;
} else {
throw new \LogicException(sprintf('Multiple "%s" annotations are not allowed.', $configuration->getAliasName()));
}
}
}
return $configurations;
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => 'onKernelController',
);
}
}

View File

@@ -0,0 +1,167 @@
<?php
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\EventListener;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
/**
* HttpCacheListener handles HTTP cache headers.
*
* It can be configured via the Cache annotation.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HttpCacheListener implements EventSubscriberInterface
{
private $lastModifiedDates;
private $etags;
private $expressionLanguage;
public function __construct()
{
$this->lastModifiedDates = new \SplObjectStorage();
$this->etags = new \SplObjectStorage();
}
/**
* Handles HTTP validation headers.
*/
public function onKernelController(FilterControllerEvent $event)
{
$request = $event->getRequest();
if (!$configuration = $request->attributes->get('_cache')) {
return;
}
$response = new Response();
$lastModifiedDate = '';
if ($configuration->getLastModified()) {
$lastModifiedDate = $this->getExpressionLanguage()->evaluate($configuration->getLastModified(), $request->attributes->all());
$response->setLastModified($lastModifiedDate);
}
$etag = '';
if ($configuration->getETag()) {
$etag = hash('sha256', $this->getExpressionLanguage()->evaluate($configuration->getETag(), $request->attributes->all()));
$response->setETag($etag);
}
if ($response->isNotModified($request)) {
$event->setController(function () use ($response) {
return $response;
});
} else {
if ($etag) {
$this->etags[$request] = $etag;
}
if ($lastModifiedDate) {
$this->lastModifiedDates[$request] = $lastModifiedDate;
}
}
}
/**
* Modifies the response to apply HTTP cache headers when needed.
*/
public function onKernelResponse(FilterResponseEvent $event)
{
$request = $event->getRequest();
if (!$configuration = $request->attributes->get('_cache')) {
return;
}
$response = $event->getResponse();
// http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-12#section-3.1
if (!in_array($response->getStatusCode(), array(200, 203, 300, 301, 302, 304, 404, 410))) {
return;
}
if (null !== $age = $configuration->getSMaxAge()) {
if (!is_numeric($age)) {
$now = microtime(true);
$age = ceil(strtotime($configuration->getSMaxAge(), $now) - $now);
}
$response->setSharedMaxAge($age);
}
if (null !== $age = $configuration->getMaxAge()) {
if (!is_numeric($age)) {
$now = microtime(true);
$age = ceil(strtotime($configuration->getMaxAge(), $now) - $now);
}
$response->setMaxAge($age);
}
if (null !== $configuration->getExpires()) {
$date = \DateTime::createFromFormat('U', strtotime($configuration->getExpires()), new \DateTimeZone('UTC'));
$response->setExpires($date);
}
if (null !== $configuration->getVary()) {
$response->setVary($configuration->getVary());
}
if ($configuration->isPublic()) {
$response->setPublic();
}
if ($configuration->isPrivate()) {
$response->setPrivate();
}
if (isset($this->lastModifiedDates[$request])) {
$response->setLastModified($this->lastModifiedDates[$request]);
unset($this->lastModifiedDates[$request]);
}
if (isset($this->etags[$request])) {
$response->setETag($this->etags[$request]);
unset($this->etags[$request]);
}
$event->setResponse($response);
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => 'onKernelController',
KernelEvents::RESPONSE => 'onKernelResponse',
);
}
private function getExpressionLanguage()
{
if (null === $this->expressionLanguage) {
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
}
$this->expressionLanguage = new ExpressionLanguage();
}
return $this->expressionLanguage;
}
}

View File

@@ -0,0 +1,116 @@
<?php
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\EventListener;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterManager;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* The ParamConverterListener handles the ParamConverter annotation.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ParamConverterListener implements EventSubscriberInterface
{
/**
* @var ParamConverterManager
*/
protected $manager;
protected $autoConvert;
/**
* Constructor.
*
* @param ParamConverterManager $manager A ParamConverterManager instance
* @param bool $autoConvert Auto convert non-configured objects
*/
public function __construct(ParamConverterManager $manager, $autoConvert = true)
{
$this->manager = $manager;
$this->autoConvert = $autoConvert;
}
/**
* Modifies the ParamConverterManager instance.
*
* @param FilterControllerEvent $event A FilterControllerEvent instance
*/
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
$request = $event->getRequest();
$configurations = array();
if ($configuration = $request->attributes->get('_converters')) {
foreach (is_array($configuration) ? $configuration : array($configuration) as $configuration) {
$configurations[$configuration->getName()] = $configuration;
}
}
if (is_array($controller)) {
$r = new \ReflectionMethod($controller[0], $controller[1]);
} elseif (is_object($controller) && is_callable($controller, '__invoke')) {
$r = new \ReflectionMethod($controller, '__invoke');
} else {
$r = new \ReflectionFunction($controller);
}
// automatically apply conversion for non-configured objects
if ($this->autoConvert) {
$configurations = $this->autoConfigure($r, $request, $configurations);
}
$this->manager->apply($request, $configurations);
}
private function autoConfigure(\ReflectionFunctionAbstract $r, Request $request, $configurations)
{
foreach ($r->getParameters() as $param) {
if (!$param->getClass() || $param->getClass()->isInstance($request)) {
continue;
}
$name = $param->getName();
if (!isset($configurations[$name])) {
$configuration = new ParamConverter(array());
$configuration->setName($name);
$configuration->setClass($param->getClass()->getName());
$configurations[$name] = $configuration;
} elseif (null === $configurations[$name]->getClass()) {
$configurations[$name]->setClass($param->getClass()->getName());
}
$configurations[$name]->setIsOptional($param->isOptional());
}
return $configurations;
}
/**
* Get subscribed events.
*
* @return array Subscribed events
*/
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => 'onKernelController',
);
}
}

View File

@@ -0,0 +1,63 @@
<?php
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\EventListener;
use Psr\Http\Message\ResponseInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Converts PSR-7 Response to HttpFoundation Response using the bridge.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class PsrResponseListener implements EventSubscriberInterface
{
/**
* @var HttpFoundationFactoryInterface
*/
private $httpFoundationFactory;
public function __construct(HttpFoundationFactoryInterface $httpFoundationFactory)
{
$this->httpFoundationFactory = $httpFoundationFactory;
}
/**
* Do the conversion if applicable and update the response of the event.
*
* @param GetResponseForControllerResultEvent $event
*/
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$controllerResult = $event->getControllerResult();
if (!$controllerResult instanceof ResponseInterface) {
return;
}
$event->setResponse($this->httpFoundationFactory->createResponse($controllerResult));
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
KernelEvents::VIEW => 'onKernelView',
);
}
}

View File

@@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\EventListener;
use Sensio\Bundle\FrameworkExtraBundle\Security\ExpressionLanguage;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
/**
* SecurityListener handles security restrictions on controllers.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SecurityListener implements EventSubscriberInterface
{
private $tokenStorage;
private $authChecker;
private $language;
private $trustResolver;
private $roleHierarchy;
public function __construct(SecurityContextInterface $securityContext = null, ExpressionLanguage $language = null, AuthenticationTrustResolverInterface $trustResolver = null, RoleHierarchyInterface $roleHierarchy = null, TokenStorageInterface $tokenStorage = null, AuthorizationCheckerInterface $authChecker = null)
{
$this->tokenStorage = $tokenStorage ?: $securityContext;
$this->authChecker = $authChecker ?: $securityContext;
$this->language = $language;
$this->trustResolver = $trustResolver;
$this->roleHierarchy = $roleHierarchy;
}
public function onKernelController(FilterControllerEvent $event)
{
$request = $event->getRequest();
if (!$configuration = $request->attributes->get('_security')) {
return;
}
if (null === $this->tokenStorage || null === $this->trustResolver) {
throw new \LogicException('To use the @Security tag, you need to install the Symfony Security bundle.');
}
if (null === $this->tokenStorage->getToken()) {
throw new \LogicException('To use the @Security tag, your controller needs to be behind a firewall.');
}
if (null === $this->language) {
throw new \LogicException('To use the @Security tag, you need to use the Security component 2.4 or newer and to install the ExpressionLanguage component.');
}
if (!$this->language->evaluate($configuration->getExpression(), $this->getVariables($request))) {
throw new AccessDeniedException(sprintf('Expression "%s" denied access.', $configuration->getExpression()));
}
}
// code should be sync with Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter
private function getVariables(Request $request)
{
$token = $this->tokenStorage->getToken();
if (null !== $this->roleHierarchy) {
$roles = $this->roleHierarchy->getReachableRoles($token->getRoles());
} else {
$roles = $token->getRoles();
}
$variables = array(
'token' => $token,
'user' => $token->getUser(),
'object' => $request,
'request' => $request,
'roles' => array_map(function ($role) { return $role->getRole(); }, $roles),
'trust_resolver' => $this->trustResolver,
// needed for the is_granted expression function
'auth_checker' => $this->authChecker,
);
// controller variables should also be accessible
return array_merge($request->attributes->all(), $variables);
}
public static function getSubscribedEvents()
{
return array(KernelEvents::CONTROLLER => 'onKernelController');
}
}

View File

@@ -0,0 +1,158 @@
<?php
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\EventListener;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
/**
* Handles the Template annotation for actions.
*
* Depends on pre-processing of the ControllerListener.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TemplateListener implements EventSubscriberInterface
{
/**
* @var ContainerInterface
*/
protected $container;
/**
* Constructor.
*
* @param ContainerInterface $container The service container instance
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Guesses the template name to render and its variables and adds them to
* the request object.
*
* @param FilterControllerEvent $event A FilterControllerEvent instance
*/
public function onKernelController(FilterControllerEvent $event)
{
$request = $event->getRequest();
$template = $request->attributes->get('_template');
// no @Template present
if (null === $template) {
return;
}
// we need the @Template annotation object or we cannot continue
if (!$template instanceof Template) {
throw new \InvalidArgumentException('Request attribute "_template" is reserved for @Template annotations.');
}
$template->setOwner($controller = $event->getController());
// when no template has been given, try to resolve it based on the controller
if (null === $template->getTemplate()) {
$guesser = $this->container->get('sensio_framework_extra.view.guesser');
$template->setTemplate($guesser->guessTemplateName($controller, $request, $template->getEngine()));
}
}
/**
* Renders the template and initializes a new response object with the
* rendered template content.
*
* @param GetResponseForControllerResultEvent $event
*/
public function onKernelView(GetResponseForControllerResultEvent $event)
{
/* @var Template $template */
$request = $event->getRequest();
$template = $request->attributes->get('_template');
if (null === $template) {
return;
}
$parameters = $event->getControllerResult();
$owner = $template->getOwner();
list($controller, $action) = $owner;
// when the annotation declares no default vars and the action returns
// null, all action method arguments are used as default vars
if (null === $parameters) {
$parameters = $this->resolveDefaultParameters($request, $template, $controller, $action);
}
// attempt to render the actual response
$templating = $this->container->get('templating');
if ($template->isStreamable()) {
$callback = function () use ($templating, $template, $parameters) {
return $templating->stream($template->getTemplate(), $parameters);
};
$event->setResponse(new StreamedResponse($callback));
}
// make sure the owner (controller+dependencies) is not cached or stored elsewhere
$template->setOwner(array());
$event->setResponse($templating->renderResponse($template->getTemplate(), $parameters));
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => array('onKernelController', -128),
KernelEvents::VIEW => 'onKernelView',
);
}
/**
* @param Request $request
* @param Template $template
* @param object $controller
* @param string $action
*
* @return array
*/
private function resolveDefaultParameters(Request $request, Template $template, $controller, $action)
{
$parameters = array();
$arguments = $template->getVars();
if (0 === count($arguments)) {
$r = new \ReflectionObject($controller);
$arguments = array();
foreach ($r->getMethod($action)->getParameters() as $param) {
$arguments[] = $param->getName();
}
}
// fetch the arguments of @Template.vars or everything if desired
// and assign them to the designated template
foreach ($arguments as $argument) {
$parameters[$argument] = $request->attributes->get($argument);
}
return $parameters;
}
}

View File

@@ -0,0 +1,19 @@
Copyright (c) 2010,2013 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,12 @@
SensioFrameworkExtraBundle
==========================
This bundle provides a way to configure your controllers with annotations.
Read about it on its [official homepage](http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html).
As of v3.0.0 of the bundle, the release cycle is de-synchronized from the
framework's. It means you can just require "sensio/framework-extra-bundle":
"~3.0" in your composer.json and Composer will automatically pick the latest
version of the bundle that works with your current version of Symfony. The
minimum version of Symfony for this workflow is 2.3.

View File

@@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use DateTime;
/**
* Convert DateTime instances from request attribute variable.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class DateTimeParamConverter implements ParamConverterInterface
{
/**
* {@inheritdoc}
*
* @throws NotFoundHttpException When invalid date given
*/
public function apply(Request $request, ParamConverter $configuration)
{
$param = $configuration->getName();
if (!$request->attributes->has($param)) {
return false;
}
$options = $configuration->getOptions();
$value = $request->attributes->get($param);
if (!$value && $configuration->isOptional()) {
return false;
}
if (isset($options['format'])) {
$date = DateTime::createFromFormat($options['format'], $value);
if (!$date) {
throw new NotFoundHttpException('Invalid date given.');
}
} else {
if (false === strtotime($value)) {
throw new NotFoundHttpException('Invalid date given.');
}
$date = new DateTime($value);
}
$request->attributes->set($param, $date);
return true;
}
/**
* {@inheritdoc}
*/
public function supports(ParamConverter $configuration)
{
if (null === $configuration->getClass()) {
return false;
}
return 'DateTime' === $configuration->getClass();
}
}

View File

@@ -0,0 +1,248 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\NoResultException;
/**
* DoctrineParamConverter.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DoctrineParamConverter implements ParamConverterInterface
{
/**
* @var ManagerRegistry
*/
protected $registry;
public function __construct(ManagerRegistry $registry = null)
{
$this->registry = $registry;
}
/**
* {@inheritdoc}
*
* @throws \LogicException When unable to guess how to get a Doctrine instance from the request information
* @throws NotFoundHttpException When object not found
*/
public function apply(Request $request, ParamConverter $configuration)
{
$name = $configuration->getName();
$class = $configuration->getClass();
$options = $this->getOptions($configuration);
if (null === $request->attributes->get($name, false)) {
$configuration->setIsOptional(true);
}
// find by identifier?
if (false === $object = $this->find($class, $request, $options, $name)) {
// find by criteria
if (false === $object = $this->findOneBy($class, $request, $options)) {
if ($configuration->isOptional()) {
$object = null;
} else {
throw new \LogicException('Unable to guess how to get a Doctrine instance from the request information.');
}
}
}
if (null === $object && false === $configuration->isOptional()) {
throw new NotFoundHttpException(sprintf('%s object not found.', $class));
}
$request->attributes->set($name, $object);
return true;
}
protected function find($class, Request $request, $options, $name)
{
if ($options['mapping'] || $options['exclude']) {
return false;
}
$id = $this->getIdentifier($request, $options, $name);
if (false === $id || null === $id) {
return false;
}
if (isset($options['repository_method'])) {
$method = $options['repository_method'];
} else {
$method = 'find';
}
try {
return $this->getManager($options['entity_manager'], $class)->getRepository($class)->$method($id);
} catch (NoResultException $e) {
return;
}
}
protected function getIdentifier(Request $request, $options, $name)
{
if (isset($options['id'])) {
if (!is_array($options['id'])) {
$name = $options['id'];
} elseif (is_array($options['id'])) {
$id = array();
foreach ($options['id'] as $field) {
$id[$field] = $request->attributes->get($field);
}
return $id;
}
}
if ($request->attributes->has($name)) {
return $request->attributes->get($name);
}
if ($request->attributes->has('id') && !isset($options['id'])) {
return $request->attributes->get('id');
}
return false;
}
protected function findOneBy($class, Request $request, $options)
{
if (!$options['mapping']) {
$keys = $request->attributes->keys();
$options['mapping'] = $keys ? array_combine($keys, $keys) : array();
}
foreach ($options['exclude'] as $exclude) {
unset($options['mapping'][$exclude]);
}
if (!$options['mapping']) {
return false;
}
// if a specific id has been defined in the options and there is no corresponding attribute
// return false in order to avoid a fallback to the id which might be of another object
if (isset($options['id']) && null === $request->attributes->get($options['id'])) {
return false;
}
$criteria = array();
$em = $this->getManager($options['entity_manager'], $class);
$metadata = $em->getClassMetadata($class);
$mapMethodSignature = isset($options['repository_method'])
&& isset($options['map_method_signature'])
&& $options['map_method_signature'] === true;
foreach ($options['mapping'] as $attribute => $field) {
if ($metadata->hasField($field)
|| ($metadata->hasAssociation($field) && $metadata->isSingleValuedAssociation($field))
|| $mapMethodSignature) {
$criteria[$field] = $request->attributes->get($attribute);
}
}
if ($options['strip_null']) {
$criteria = array_filter($criteria, function ($value) { return !is_null($value); });
}
if (!$criteria) {
return false;
}
if (isset($options['repository_method'])) {
$repositoryMethod = $options['repository_method'];
} else {
$repositoryMethod = 'findOneBy';
}
try {
if ($mapMethodSignature) {
return $this->findDataByMapMethodSignature($em, $class, $repositoryMethod, $criteria);
}
return $em->getRepository($class)->$repositoryMethod($criteria);
} catch (NoResultException $e) {
return;
}
}
private function findDataByMapMethodSignature($em, $class, $repositoryMethod, $criteria)
{
$arguments = array();
$repository = $em->getRepository($class);
$ref = new \ReflectionMethod($repository, $repositoryMethod);
foreach ($ref->getParameters() as $parameter) {
if (array_key_exists($parameter->name, $criteria)) {
$arguments[] = $criteria[$parameter->name];
} elseif ($parameter->isDefaultValueAvailable()) {
$arguments[] = $parameter->getDefaultValue();
} else {
throw new \InvalidArgumentException(sprintf('Repository method "%s::%s" requires that you provide a value for the "$%s" argument.', get_class($repository), $repositoryMethod, $parameter->name));
}
}
return $ref->invokeArgs($repository, $arguments);
}
/**
* {@inheritdoc}
*/
public function supports(ParamConverter $configuration)
{
// if there is no manager, this means that only Doctrine DBAL is configured
if (null === $this->registry || !count($this->registry->getManagers())) {
return false;
}
if (null === $configuration->getClass()) {
return false;
}
$options = $this->getOptions($configuration);
// Doctrine Entity?
$em = $this->getManager($options['entity_manager'], $configuration->getClass());
if (null === $em) {
return false;
}
return !$em->getMetadataFactory()->isTransient($configuration->getClass());
}
protected function getOptions(ParamConverter $configuration)
{
return array_replace(array(
'entity_manager' => null,
'exclude' => array(),
'mapping' => array(),
'strip_null' => false,
), $configuration->getOptions());
}
private function getManager($name, $class)
{
if (null === $name) {
return $this->registry->getManagerForClass($class);
}
return $this->registry->getManager($name);
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
/**
* Converts request parameters to objects and stores them as request
* attributes, so they can be injected as controller method arguments.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ParamConverterInterface
{
/**
* Stores the object in the request.
*
* @param Request $request The request
* @param ParamConverter $configuration Contains the name, class and options of the object
*
* @return bool True if the object has been successfully set, else false
*/
public function apply(Request $request, ParamConverter $configuration);
/**
* Checks if the object is supported.
*
* @param ParamConverter $configuration Should be an instance of ParamConverter
*
* @return bool True if the object is supported, else false
*/
public function supports(ParamConverter $configuration);
}

View File

@@ -0,0 +1,144 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface;
/**
* Managers converters.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Henrik Bjornskov <henrik@bjrnskov.dk>
*/
class ParamConverterManager
{
/**
* @var array
*/
protected $converters = array();
/**
* @var array
*/
protected $namedConverters = array();
/**
* Applies all converters to the passed configurations and stops when a
* converter is applied it will move on to the next configuration and so on.
*
* @param Request $request
* @param array|object $configurations
*/
public function apply(Request $request, $configurations)
{
if (is_object($configurations)) {
$configurations = array($configurations);
}
foreach ($configurations as $configuration) {
$this->applyConverter($request, $configuration);
}
}
/**
* Apply converter on request based on the given configuration.
*
* @param Request $request
* @param ConfigurationInterface $configuration
*/
protected function applyConverter(Request $request, ConfigurationInterface $configuration)
{
$value = $request->attributes->get($configuration->getName());
$className = $configuration->getClass();
// If the value is already an instance of the class we are trying to convert it into
// we should continue as no conversion is required
if (is_object($value) && $value instanceof $className) {
return;
}
if ($converterName = $configuration->getConverter()) {
if (!isset($this->namedConverters[$converterName])) {
throw new \RuntimeException(sprintf(
"No converter named '%s' found for conversion of parameter '%s'.",
$converterName, $configuration->getName()
));
}
$converter = $this->namedConverters[$converterName];
if (!$converter->supports($configuration)) {
throw new \RuntimeException(sprintf(
"Converter '%s' does not support conversion of parameter '%s'.",
$converterName, $configuration->getName()
));
}
$converter->apply($request, $configuration);
return;
}
foreach ($this->all() as $converter) {
if ($converter->supports($configuration)) {
if ($converter->apply($request, $configuration)) {
return;
}
}
}
}
/**
* Adds a parameter converter.
*
* Converters match either explicitly via $name or by iteration over all
* converters with a $priority. If you pass a $priority = null then the
* added converter will not be part of the iteration chain and can only
* be invoked explicitly.
*
* @param ParamConverterInterface $converter A ParamConverterInterface instance
* @param int $priority The priority (between -10 and 10).
* @param string $name Name of the converter.
*/
public function add(ParamConverterInterface $converter, $priority = 0, $name = null)
{
if ($priority !== null) {
if (!isset($this->converters[$priority])) {
$this->converters[$priority] = array();
}
$this->converters[$priority][] = $converter;
}
if (null !== $name) {
$this->namedConverters[$name] = $converter;
}
}
/**
* Returns all registered param converters.
*
* @return array An array of param converters
*/
public function all()
{
krsort($this->converters);
$converters = array();
foreach ($this->converters as $all) {
$converters = array_merge($converters, $all);
}
return $converters;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Converts HttpFoundation Request to PSR-7 ServerRequest using the bridge.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class PsrServerRequestParamConverter implements ParamConverterInterface
{
/**
* @var array
*/
private static $supportedTypes = array(
'Psr\Http\Message\ServerRequestInterface' => true,
'Psr\Http\Message\RequestInterface' => true,
'Psr\Http\Message\MessageInterface' => true,
);
/**
* @var HttpMessageFactoryInterface
*/
private $httpMessageFactory;
public function __construct(HttpMessageFactoryInterface $httpMessageFactory)
{
$this->httpMessageFactory = $httpMessageFactory;
}
/**
* {@inheritdoc}
*/
public function apply(Request $request, ParamConverter $configuration)
{
$request->attributes->set($configuration->getName(), $this->httpMessageFactory->createRequest($request));
}
/**
* {@inheritdoc}
*/
public function supports(ParamConverter $configuration)
{
if (null === $configuration->getClass()) {
return false;
}
return isset(self::$supportedTypes[$configuration->getClass()]);
}
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="sensio_framework_extra.controller.listener.class">Sensio\Bundle\FrameworkExtraBundle\EventListener\ControllerListener</parameter>
</parameters>
<services>
<service id="sensio_framework_extra.controller.listener" class="%sensio_framework_extra.controller.listener.class%">
<tag name="kernel.event_subscriber" />
<argument type="service" id="annotation_reader" />
</service>
</services>
</container>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="sensio_framework_extra.cache.listener" class="Sensio\Bundle\FrameworkExtraBundle\EventListener\HttpCacheListener">
<tag name="kernel.event_subscriber" />
</service>
</services>
</container>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="sensio_framework_extra.converter.listener.class">Sensio\Bundle\FrameworkExtraBundle\EventListener\ParamConverterListener</parameter>
<parameter key="sensio_framework_extra.converter.manager.class">Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterManager</parameter>
<parameter key="sensio_framework_extra.converter.doctrine.class">Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\DoctrineParamConverter</parameter>
<parameter key="sensio_framework_extra.converter.datetime.class">Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\DateTimeParamConverter</parameter>
</parameters>
<services>
<service id="sensio_framework_extra.converter.listener" class="%sensio_framework_extra.converter.listener.class%">
<tag name="kernel.event_subscriber" />
<argument type="service" id="sensio_framework_extra.converter.manager" />
<argument>true</argument>
</service>
<service id="sensio_framework_extra.converter.manager" class="%sensio_framework_extra.converter.manager.class%" />
<service id="sensio_framework_extra.converter.doctrine.orm" class="%sensio_framework_extra.converter.doctrine.class%">
<tag name="request.param_converter" converter="doctrine.orm" />
<argument type="service" id="doctrine" on-invalid="ignore" />
</service>
<service id="sensio_framework_extra.converter.datetime" class="%sensio_framework_extra.converter.datetime.class%">
<tag name="request.param_converter" converter="datetime" />
</service>
</services>
</container>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="sensio_framework_extra.psr7.http_message_factory" class="Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory" public="false" />
<service id="sensio_framework_extra.psr7.http_foundation_factory" class="Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory" public="false" />
<service id="sensio_framework_extra.psr7.converter.server_request" class="Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\PsrServerRequestParamConverter">
<argument type="service" id="sensio_framework_extra.psr7.http_message_factory" />
<tag name="request.param_converter" converter="psr.server_request" />
</service>
<service id="sensio_framework_extra.psr7.listener.response" class="Sensio\Bundle\FrameworkExtraBundle\EventListener\PsrResponseListener">
<argument type="service" id="sensio_framework_extra.psr7.http_foundation_factory" />
<tag name="kernel.event_subscriber" />
</service>
</services>
</container>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="sensio_framework_extra.routing.loader.annot_dir.class">Symfony\Component\Routing\Loader\AnnotationDirectoryLoader</parameter>
<parameter key="sensio_framework_extra.routing.loader.annot_file.class">Symfony\Component\Routing\Loader\AnnotationFileLoader</parameter>
<parameter key="sensio_framework_extra.routing.loader.annot_class.class">Sensio\Bundle\FrameworkExtraBundle\Routing\AnnotatedRouteControllerLoader</parameter>
</parameters>
<services>
<service id="sensio_framework_extra.routing.loader.annot_dir" class="%sensio_framework_extra.routing.loader.annot_dir.class%" public="false">
<tag name="routing.loader" />
<argument type="service" id="file_locator" />
<argument type="service" id="sensio_framework_extra.routing.loader.annot_class" />
</service>
<service id="sensio_framework_extra.routing.loader.annot_file" class="%sensio_framework_extra.routing.loader.annot_file.class%" public="false">
<tag name="routing.loader" />
<argument type="service" id="file_locator" />
<argument type="service" id="sensio_framework_extra.routing.loader.annot_class" />
</service>
<service id="sensio_framework_extra.routing.loader.annot_class" class="%sensio_framework_extra.routing.loader.annot_class.class%" public="false">
<tag name="routing.loader" />
<argument type="service" id="annotation_reader" />
</service>
</services>
</container>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="sensio_framework_extra.security.listener" class="Sensio\Bundle\FrameworkExtraBundle\EventListener\SecurityListener">
<argument type="service" id="security.context" on-invalid="null" />
<argument type="service" id="sensio_framework_extra.security.expression_language" on-invalid="null" />
<argument type="service" id="security.authentication.trust_resolver" on-invalid="null" />
<argument type="service" id="security.role_hierarchy" on-invalid="null" />
<argument type="service" id="security.token_storage" on-invalid="null" />
<argument type="service" id="security.authorization_checker" on-invalid="null" />
<tag name="kernel.event_subscriber" />
</service>
<service id="sensio_framework_extra.security.expression_language.default" class="Sensio\Bundle\FrameworkExtraBundle\Security\ExpressionLanguage" public="false" />
</services>
</container>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="sensio_framework_extra.view.guesser.class">Sensio\Bundle\FrameworkExtraBundle\Templating\TemplateGuesser</parameter>
</parameters>
<services>
<service id="sensio_framework_extra.view.guesser" class="%sensio_framework_extra.view.guesser.class%">
<argument type="service" id="kernel" />
</service>
</services>
</container>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="sensio_framework_extra.view.listener.class">Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener</parameter>
</parameters>
<services>
<service id="sensio_framework_extra.view.listener" class="%sensio_framework_extra.view.listener.class%">
<tag name="kernel.event_subscriber" />
<argument type="service" id="service_container" />
</service>
</services>
</container>

View File

@@ -0,0 +1,95 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Routing;
use Symfony\Component\Routing\Loader\AnnotationClassLoader;
use Symfony\Component\Routing\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route as FrameworkExtraBundleRoute;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
/**
* AnnotatedRouteControllerLoader is an implementation of AnnotationClassLoader
* that sets the '_controller' default based on the class and method names.
*
* It also parse the @Method annotation.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class AnnotatedRouteControllerLoader extends AnnotationClassLoader
{
/**
* Configures the _controller default parameter and eventually the HTTP method
* requirement of a given Route instance.
*
* @param Route $route A route instance
* @param \ReflectionClass $class A ReflectionClass instance
* @param \ReflectionMethod $method A ReflectionClass method
* @param mixed $annot The annotation class instance
*
* @throws \LogicException When the service option is specified on a method
*/
protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot)
{
// controller
$classAnnot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass);
if ($classAnnot instanceof FrameworkExtraBundleRoute && $service = $classAnnot->getService()) {
$route->setDefault('_controller', $service.':'.$method->getName());
} else {
$route->setDefault('_controller', $class->getName().'::'.$method->getName());
}
// requirements (@Method)
foreach ($this->reader->getMethodAnnotations($method) as $configuration) {
if ($configuration instanceof Method) {
$route->setMethods(implode('|', $configuration->getMethods()));
} elseif ($configuration instanceof FrameworkExtraBundleRoute && $configuration->getService()) {
throw new \LogicException('The service option can only be specified at class level.');
}
}
}
protected function getGlobals(\ReflectionClass $class)
{
$globals = parent::getGlobals($class);
foreach ($this->reader->getClassAnnotations($class) as $configuration) {
if ($configuration instanceof Method) {
$globals['methods'] = array_merge($globals['methods'], $configuration->getMethods());
}
}
return $globals;
}
/**
* Makes the default route name more sane by removing common keywords.
*
* @param \ReflectionClass $class A ReflectionClass instance
* @param \ReflectionMethod $method A ReflectionMethod instance
*
* @return string The default route name
*/
protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method)
{
$routeName = parent::getDefaultRouteName($class, $method);
return preg_replace(array(
'/(bundle|controller)_/',
'/action(_\d+)?$/',
'/__/',
), array(
'_',
'\\1',
'_',
), $routeName);
}
}

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Security;
use Symfony\Component\Security\Core\Authorization\ExpressionLanguage as BaseExpressionLanguage;
/**
* Adds some function to the default Symfony Security ExpressionLanguage.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ExpressionLanguage extends BaseExpressionLanguage
{
protected function registerFunctions()
{
parent::registerFunctions();
$this->register('is_granted', function ($attributes, $object = 'null') {
return sprintf('$auth_checker->isGranted(%s, %s)', $attributes, $object);
}, function (array $variables, $attributes, $object = null) {
return $variables['auth_checker']->isGranted($attributes, $object);
});
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Sensio\Bundle\FrameworkExtraBundle\DependencyInjection\Compiler\AddParamConverterPass;
use Sensio\Bundle\FrameworkExtraBundle\DependencyInjection\Compiler\LegacyPass;
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* SensioFrameworkExtraBundle.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SensioFrameworkExtraBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new AddParamConverterPass());
$container->addCompilerPass(new LegacyPass());
}
}

View File

@@ -0,0 +1,133 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Templating;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
use Doctrine\Common\Util\ClassUtils;
/**
* The TemplateGuesser class handles the guessing of template name based on controller.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TemplateGuesser
{
/**
* @var KernelInterface
*/
protected $kernel;
/**
* @var string[]
*/
private $controllerPatterns;
/**
* Constructor.
*
* @param KernelInterface $kernel A KernelInterface instance
* @param string[] $controllerPatterns Regexps extracting the controller name from its FQN.
*/
public function __construct(KernelInterface $kernel, array $controllerPatterns = array())
{
$controllerPatterns[] = '/Controller\\\(.+)Controller$/';
$this->kernel = $kernel;
$this->controllerPatterns = $controllerPatterns;
}
/**
* Guesses and returns the template name to render based on the controller
* and action names.
*
* @param callable $controller An array storing the controller object and action method
* @param Request $request A Request instance
* @param string $engine
*
* @return TemplateReference template reference
*
* @throws \InvalidArgumentException
*/
public function guessTemplateName($controller, Request $request, $engine = 'twig')
{
if (is_object($controller) && method_exists($controller, '__invoke')) {
$controller = array($controller, '__invoke');
} elseif (!is_array($controller)) {
throw new \InvalidArgumentException(sprintf('First argument of %s must be an array callable or an object defining the magic method __invoke. "%s" given.', __METHOD__, gettype($controller)));
}
$className = class_exists('Doctrine\Common\Util\ClassUtils') ? ClassUtils::getClass($controller[0]) : get_class($controller[0]);
$matchController = null;
foreach ($this->controllerPatterns as $pattern) {
if (preg_match($pattern, $className, $tempMatch)) {
$matchController = $tempMatch;
break;
}
}
if (null === $matchController) {
throw new \InvalidArgumentException(sprintf('The "%s" class does not look like a controller class (its FQN must match one of the following regexps: "%s")', get_class($controller[0]), implode('", "', $this->controllerPatterns)));
}
if ($controller[1] === '__invoke') {
$matchAction = $matchController;
$matchController = null;
} elseif (!preg_match('/^(.+)Action$/', $controller[1], $matchAction)) {
$matchAction = array(null, $controller[1]);
}
$bundle = $this->getBundleForClass($className);
if ($bundle) {
while ($bundleName = $bundle->getName()) {
if (null === $parentBundleName = $bundle->getParent()) {
$bundleName = $bundle->getName();
break;
}
$bundles = $this->kernel->getBundle($parentBundleName, false);
$bundle = array_pop($bundles);
}
} else {
$bundleName = null;
}
return new TemplateReference($bundleName, $matchController[1], $matchAction[1], $request->getRequestFormat(), $engine);
}
/**
* Returns the Bundle instance in which the given class name is located.
*
* @param string $class A fully qualified controller class name
*
* @return Bundle|null $bundle A Bundle instance
*/
protected function getBundleForClass($class)
{
$reflectionClass = new \ReflectionClass($class);
$bundles = $this->kernel->getBundles();
do {
$namespace = $reflectionClass->getNamespaceName();
foreach ($bundles as $bundle) {
if (0 === strpos($namespace, $bundle->getNamespace())) {
return $bundle;
}
}
$reflectionClass = $reflectionClass->getParentClass();
} while ($reflectionClass);
}
}

View File

@@ -0,0 +1,50 @@
UPGRADE FROM 2.0 to 2.1
=======================
### DoctrineParamConverter: Request Attributes with same name as Arguments
Previously the DoctrineParamConverter defaulted to finding objects by 'id'
parameter. This is unintuitive and is now overwritten by a behavior where
the request attributes with the same name as entity arguments is matched
with higher priority.
This might cause problems if you are using this parameter for another object conversion.
### DoctrineParamConverter with multiple Arguments may clash
In 2.0 the parameter converter matched only entity fields against route parameters.
With 2.1, the matching now also includes single-valued associations. Depending
on fields in entities this might lead to clashes when you update to the latest version.
Example that may break with the latest (2.1) version:
/**
* @Route("/user/{email}/{address}")
* @ParamConverter("address", class="MyBundle:Address", options={"id": "address"})
*/
public function showAction(User $user, Address $address)
{
}
class User
{
/** @ORM\Column(type="string") */
public $email;
/** @ORM\ManyToOne(targetEntity="Address") */
public $address;
}
Since address exists as field in `User` and User is not searched by primary key but
by field, this scenario now adds `address` to the criteria to find a user instance.
In scenarios of related entities this might even (just) work, but you never know.
You can fix this by configuring explicit mapping for `User`:
/**
* @Route("/user/{email}/{address}")
* @ParamConverter("address", options={"id": "address"})
* @ParamConverter("email", options={"exclude": ["address"]})
*/
public function showAction(User $user, Address $address)
{
}

View File

@@ -0,0 +1,45 @@
{
"name": "sensio/framework-extra-bundle",
"description": "This bundle provides a way to configure your controllers with annotations",
"keywords": ["annotations","controllers"],
"type": "symfony-bundle",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
}
],
"require": {
"symfony/framework-bundle": "~2.3|~3.0",
"symfony/dependency-injection": "~2.3|~3.0",
"doctrine/common": "~2.2"
},
"require-dev": {
"symfony/expression-language": "~2.4|~3.0",
"symfony/finder": "~2.3|~3.0",
"symfony/security-bundle": "~2.4|~3.0",
"symfony/twig-bundle": "~2.3|~3.0",
"twig/twig": "~1.11|~2.0",
"symfony/browser-kit": "~2.3|~3.0",
"symfony/phpunit-bridge": "~2.7|~3.0",
"symfony/dom-crawler": "~2.3|~3.0"
},
"suggest": {
"symfony/psr-http-message-bridge": "To use the PSR-7 converters",
"symfony/expression-language": "",
"symfony/security-bundle": ""
},
"autoload": {
"psr-4": { "Sensio\\Bundle\\FrameworkExtraBundle\\": "" }
},
"autoload-dev": {
"psr-4": { "Tests\\Fixtures\\": "Tests/Fixtures/" }
},
"extra": {
"branch-alias": {
"dev-master": "3.0.x-dev"
}
},
"minimum-stability": "dev"
}

View File

@@ -0,0 +1,21 @@
language: php
sudo: false
cache:
directories:
- $HOME/.composer/cache
matrix:
include:
- php: hhvm
- php: 5.3
- php: 5.4
- php: 5.5
- php: 5.6
- php: 7.0
env: composer_extra="--prefer-lowest --prefer-stable"
fast_finish: true
install:
- composer --prefer-source $composer_extra update

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Command\AutoComplete;
use Doctrine\ORM\EntityManagerInterface;
/**
* Provides auto-completion suggestions for entities.
*
* @author Charles Sarrazin <charles@sarraz.in>
*/
class EntitiesAutoCompleter
{
private $manager;
public function __construct(EntityManagerInterface $manager)
{
$this->manager = $manager;
}
public function getSuggestions()
{
$configuration = $this->manager
->getConfiguration()
;
$namespaceReplacements = array();
foreach ($configuration->getEntityNamespaces() as $alias => $namespace) {
$namespaceReplacements[$namespace.'\\'] = $alias.':';
}
$entities = $configuration
->getMetadataDriverImpl()
->getAllClassNames()
;
return array_map(function ($entity) use ($namespaceReplacements) {
return strtr($entity, $namespaceReplacements);
}, $entities);
}
}

View File

@@ -0,0 +1,419 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Command;
use Sensio\Bundle\GeneratorBundle\Manipulator\ConfigurationManipulator;
use Sensio\Bundle\GeneratorBundle\Model\Bundle;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\HttpKernel\KernelInterface;
use Sensio\Bundle\GeneratorBundle\Generator\BundleGenerator;
use Sensio\Bundle\GeneratorBundle\Manipulator\KernelManipulator;
use Sensio\Bundle\GeneratorBundle\Manipulator\RoutingManipulator;
/**
* Generates bundles.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class GenerateBundleCommand extends GeneratorCommand
{
/**
* @see Command
*/
protected function configure()
{
$this
->setName('generate:bundle')
->setDescription('Generates a bundle')
->setDefinition(array(
new InputOption('namespace', '', InputOption::VALUE_REQUIRED, 'The namespace of the bundle to create'),
new InputOption('dir', '', InputOption::VALUE_REQUIRED, 'The directory where to create the bundle', 'src/'),
new InputOption('bundle-name', '', InputOption::VALUE_REQUIRED, 'The optional bundle name'),
new InputOption('format', '', InputOption::VALUE_REQUIRED, 'Use the format for configuration files (php, xml, yml, or annotation)'),
new InputOption('shared', '', InputOption::VALUE_NONE, 'Are you planning on sharing this bundle across multiple applications?'),
))
->setHelp(<<<EOT
The <info>%command.name%</info> command helps you generates new bundles.
By default, the command interacts with the developer to tweak the generation.
Any passed option will be used as a default value for the interaction
(<comment>--namespace</comment> is the only one needed if you follow the
conventions):
<info>php %command.full_name% --namespace=Acme/BlogBundle</info>
Note that you can use <comment>/</comment> instead of <comment>\\ </comment>for the namespace delimiter to avoid any
problems.
If you want to disable any user interaction, use <comment>--no-interaction</comment> but don't forget to pass all needed options:
<info>php %command.full_name% --namespace=Acme/BlogBundle --dir=src [--bundle-name=...] --no-interaction</info>
Note that the bundle namespace must end with "Bundle".
EOT
)
;
}
/**
* @see Command
*
* @throws \InvalidArgumentException When namespace doesn't end with Bundle
* @throws \RuntimeException When bundle can't be executed
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$questionHelper = $this->getQuestionHelper();
$bundle = $this->createBundleObject($input);
$questionHelper->writeSection($output, 'Bundle generation');
/** @var BundleGenerator $generator */
$generator = $this->getGenerator();
$output->writeln(sprintf(
'> Generating a sample bundle skeleton into <info>%s</info> <comment>OK!</comment>',
$this->makePathRelative($bundle->getTargetDirectory())
));
$generator->generateBundle($bundle);
$errors = array();
$runner = $questionHelper->getRunner($output, $errors);
// check that the namespace is already autoloaded
$runner($this->checkAutoloader($output, $bundle));
// register the bundle in the Kernel class
$runner($this->updateKernel($output, $this->getContainer()->get('kernel'), $bundle));
// routing importing
$runner($this->updateRouting($output, $bundle));
if (!$bundle->shouldGenerateDependencyInjectionDirectory()) {
// we need to import their services.yml manually!
$runner($this->updateConfiguration($output, $bundle));
}
$questionHelper->writeGeneratorSummary($output, $errors);
}
protected function interact(InputInterface $input, OutputInterface $output)
{
$questionHelper = $this->getQuestionHelper();
$questionHelper->writeSection($output, 'Welcome to the Symfony bundle generator!');
/*
* shared option
*/
$shared = $input->getOption('shared');
// ask, but use $shared as the default
$question = new ConfirmationQuestion($questionHelper->getQuestion(
'Are you planning on sharing this bundle across multiple applications?',
$shared ? 'yes' : 'no'
), $shared);
$shared = $questionHelper->ask($input, $output, $question);
$input->setOption('shared', $shared);
/*
* namespace option
*/
$namespace = $input->getOption('namespace');
$output->writeln(array(
'',
'Your application code must be written in <comment>bundles</comment>. This command helps',
'you generate them easily.',
'',
));
$askForBundleName = true;
if ($shared) {
// a shared bundle, so it should probably have a vendor namespace
$output->writeln(array(
'Each bundle is hosted under a namespace (like <comment>Acme/BlogBundle</comment>).',
'The namespace should begin with a "vendor" name like your company name, your',
'project name, or your client name, followed by one or more optional category',
'sub-namespaces, and it should end with the bundle name itself',
'(which must have <comment>Bundle</comment> as a suffix).',
'',
'See http://symfony.com/doc/current/cookbook/bundles/best_practices.html#bundle-name for more',
'details on bundle naming conventions.',
'',
'Use <comment>/</comment> instead of <comment>\\ </comment> for the namespace delimiter to avoid any problem.',
'',
));
$question = new Question($questionHelper->getQuestion(
'Bundle namespace',
$namespace
), $namespace);
$question->setValidator(function ($answer) {
return Validators::validateBundleNamespace($answer, true);
});
$namespace = $questionHelper->ask($input, $output, $question);
} else {
// a simple application bundle
$output->writeln(array(
'Give your bundle a descriptive name, like <comment>BlogBundle</comment>.',
));
$question = new Question($questionHelper->getQuestion(
'Bundle name',
$namespace
), $namespace);
$question->setValidator(function ($inputNamespace) {
return Validators::validateBundleNamespace($inputNamespace, false);
});
$namespace = $questionHelper->ask($input, $output, $question);
if (strpos($namespace, '\\') === false) {
// this is a bundle name (FooBundle) not a namespace (Acme\FooBundle)
// so this is the bundle name (and it is also the namespace)
$input->setOption('bundle-name', $namespace);
$askForBundleName = false;
}
}
$input->setOption('namespace', $namespace);
/*
* bundle-name option
*/
if ($askForBundleName) {
$bundle = $input->getOption('bundle-name');
// no bundle yet? Get a default from the namespace
if (!$bundle) {
$bundle = strtr($namespace, array('\\Bundle\\' => '', '\\' => ''));
}
$output->writeln(array(
'',
'In your code, a bundle is often referenced by its name. It can be the',
'concatenation of all namespace parts but it\'s really up to you to come',
'up with a unique name (a good practice is to start with the vendor name).',
'Based on the namespace, we suggest <comment>'.$bundle.'</comment>.',
'',
));
$question = new Question($questionHelper->getQuestion(
'Bundle name',
$bundle
), $bundle);
$question->setValidator(
array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateBundleName')
);
$bundle = $questionHelper->ask($input, $output, $question);
$input->setOption('bundle-name', $bundle);
}
/*
* dir option
*/
// defaults to src/ in the option
$dir = $input->getOption('dir');
$output->writeln(array(
'',
'Bundles are usually generated into the <info>src/</info> directory. Unless you\'re',
'doing something custom, hit enter to keep this default!',
'',
));
$question = new Question($questionHelper->getQuestion(
'Target Directory',
$dir
), $dir);
$dir = $questionHelper->ask($input, $output, $question);
$input->setOption('dir', $dir);
/*
* format option
*/
$format = $input->getOption('format');
if (!$format) {
$format = $shared ? 'xml' : 'annotation';
}
$output->writeln(array(
'',
'What format do you want to use for your generated configuration?',
'',
));
$question = new Question($questionHelper->getQuestion(
'Configuration format (annotation, yml, xml, php)',
$format
), $format);
$question->setValidator(function ($format) {
return Validators::validateFormat($format);
});
$question->setAutocompleterValues(array('annotation', 'yml', 'xml', 'php'));
$format = $questionHelper->ask($input, $output, $question);
$input->setOption('format', $format);
}
protected function checkAutoloader(OutputInterface $output, Bundle $bundle)
{
$output->write('> Checking that the bundle is autoloaded: ');
if (!class_exists($bundle->getBundleClassName())) {
return array(
'- Edit the <comment>composer.json</comment> file and register the bundle',
' namespace in the "autoload" section:',
'',
);
}
}
protected function updateKernel(OutputInterface $output, KernelInterface $kernel, Bundle $bundle)
{
$kernelManipulator = new KernelManipulator($kernel);
$output->write(sprintf(
'> Enabling the bundle inside <info>%s</info>: ',
$this->makePathRelative($kernelManipulator->getFilename())
));
try {
$ret = $kernelManipulator->addBundle($bundle->getBundleClassName());
if (!$ret) {
$reflected = new \ReflectionObject($kernel);
return array(
sprintf('- Edit <comment>%s</comment>', $reflected->getFilename()),
' and add the following bundle in the <comment>AppKernel::registerBundles()</comment> method:',
'',
sprintf(' <comment>new %s(),</comment>', $bundle->getBundleClassName()),
'',
);
}
} catch (\RuntimeException $e) {
return array(
sprintf('Bundle <comment>%s</comment> is already defined in <comment>AppKernel::registerBundles()</comment>.', $bundle->getBundleClassName()),
'',
);
}
}
protected function updateRouting(OutputInterface $output, Bundle $bundle)
{
$targetRoutingPath = $this->getContainer()->getParameter('kernel.root_dir').'/config/routing.yml';
$output->write(sprintf(
'> Importing the bundle\'s routes from the <info>%s</info> file: ',
$this->makePathRelative($targetRoutingPath)
));
$routing = new RoutingManipulator($targetRoutingPath);
try {
$ret = $routing->addResource($bundle->getName(), $bundle->getConfigurationFormat());
if (!$ret) {
if ('annotation' === $bundle->getConfigurationFormat()) {
$help = sprintf(" <comment>resource: \"@%s/Controller/\"</comment>\n <comment>type: annotation</comment>\n", $bundle->getName());
} else {
$help = sprintf(" <comment>resource: \"@%s/Resources/config/routing.%s\"</comment>\n", $bundle->getName(), $bundle->getConfigurationFormat());
}
$help .= " <comment>prefix: /</comment>\n";
return array(
'- Import the bundle\'s routing resource in the app\'s main routing file:',
'',
sprintf(' <comment>%s:</comment>', $bundle->getName()),
$help,
'',
);
}
} catch (\RuntimeException $e) {
return array(
sprintf('Bundle <comment>%s</comment> is already imported.', $bundle->getName()),
'',
);
}
}
protected function updateConfiguration(OutputInterface $output, Bundle $bundle)
{
$targetConfigurationPath = $this->getContainer()->getParameter('kernel.root_dir').'/config/config.yml';
$output->write(sprintf(
'> Importing the bundle\'s %s from the <info>%s</info> file: ',
$bundle->getServicesConfigurationFilename(),
$this->makePathRelative($targetConfigurationPath)
));
$manipulator = new ConfigurationManipulator($targetConfigurationPath);
try {
$manipulator->addResource($bundle);
} catch (\RuntimeException $e) {
return array(
'- Import the bundle\'s %s resource in the app\'s main configuration file:',
'',
$manipulator->getImportCode($bundle),
'',
);
}
}
/**
* Creates the Bundle object based on the user's (non-interactive) input.
*
* @param InputInterface $input
*
* @return Bundle
*/
protected function createBundleObject(InputInterface $input)
{
foreach (array('namespace', 'dir') as $option) {
if (null === $input->getOption($option)) {
throw new \RuntimeException(sprintf('The "%s" option must be provided.', $option));
}
}
$shared = $input->getOption('shared');
$namespace = Validators::validateBundleNamespace($input->getOption('namespace'), $shared);
if (!$bundleName = $input->getOption('bundle-name')) {
$bundleName = strtr($namespace, array('\\' => ''));
}
$bundleName = Validators::validateBundleName($bundleName);
$dir = $input->getOption('dir');
if (null === $input->getOption('format')) {
$input->setOption('format', 'annotation');
}
$format = Validators::validateFormat($input->getOption('format'));
// an assumption that the kernel root dir is in a directory (like app/)
$projectRootDirectory = $this->getContainer()->getParameter('kernel.root_dir').'/..';
if (!$this->getContainer()->get('filesystem')->isAbsolutePath($dir)) {
$dir = $projectRootDirectory.'/'.$dir;
}
// add trailing / if necessary
$dir = '/' === substr($dir, -1, 1) ? $dir : $dir.'/';
$bundle = new Bundle(
$namespace,
$bundleName,
$dir,
$format,
$shared
);
// not shared - put the tests in the root
if (!$shared) {
$testsDir = $projectRootDirectory.'/tests/'.$bundleName;
$bundle->setTestsDirectory($testsDir);
}
return $bundle;
}
protected function createGenerator()
{
return new BundleGenerator($this->getContainer()->get('filesystem'));
}
}

View File

@@ -0,0 +1,169 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Sensio\Bundle\GeneratorBundle\Generator\CommandGenerator;
/**
* Generates commands.
*
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
class GenerateCommandCommand extends GeneratorCommand
{
const MAX_ATTEMPTS = 5;
/**
* @see Command
*/
public function configure()
{
$this
->setName('generate:command')
->setDescription('Generates a console command')
->setDefinition(array(
new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle where the command is generated'),
new InputArgument('name', InputArgument::OPTIONAL, 'The command\'s name (e.g. app:my-command)'),
))
->setHelp(<<<EOT
The <info>generate:command</info> command helps you generate new commands
inside bundles. Provide the bundle name as the first argument and the command
name as the second argument:
<info>php app/console generate:command AppBundle blog:publish-posts</info>
If any of the arguments is missing, the command will ask for their values
interactively. If you want to disable any user interaction, use
<comment>--no-interaction</comment>, but don't forget to pass all needed arguments.
Every generated file is based on a template. There are default templates but they can
be overridden by placing custom templates in one of the following locations, by order of priority:
<info>BUNDLE_PATH/Resources/SensioGeneratorBundle/skeleton/command
APP_PATH/Resources/SensioGeneratorBundle/skeleton/command</info>
You can check https://github.com/sensio/SensioGeneratorBundle/tree/master/Resources/skeleton
in order to know the file structure of the skeleton.
EOT
)
;
}
public function interact(InputInterface $input, OutputInterface $output)
{
$bundle = $input->getArgument('bundle');
$name = $input->getArgument('name');
if (null !== $bundle && null !== $name) {
return;
}
$questionHelper = $this->getQuestionHelper();
$questionHelper->writeSection($output, 'Welcome to the Symfony command generator');
// bundle
if (null !== $bundle) {
$output->writeln(sprintf('Bundle name: %s', $bundle));
} else {
$output->writeln(array(
'',
'First, you need to give the name of the bundle where the command will',
'be generated (e.g. <comment>AppBundle</comment>)',
'',
));
$question = new Question($questionHelper->getQuestion('Bundle name', $bundle), $bundle);
$container = $this->getContainer();
$question->setValidator(function ($answer) use ($container) {
try {
$b = $container->get('kernel')->getBundle($answer);
} catch (\Exception $e) {
throw new \RuntimeException(sprintf('Bundle "%s" does not exist.', $answer));
}
return $answer;
});
$question->setMaxAttempts(self::MAX_ATTEMPTS);
$bundle = $questionHelper->ask($input, $output, $question);
$input->setArgument('bundle', $bundle);
}
// command name
if (null !== $name) {
$output->writeln(sprintf('Command name: %s', $name));
} else {
$output->writeln(array(
'',
'Now, provide the name of the command as you type it in the console',
'(e.g. <comment>app:my-command</comment>)',
'',
));
$question = new Question($questionHelper->getQuestion('Command name', $name), $name);
$question->setValidator(function ($answer) {
if (empty($answer)) {
throw new \RuntimeException('The command name cannot be empty.');
}
return $answer;
});
$question->setMaxAttempts(self::MAX_ATTEMPTS);
$name = $questionHelper->ask($input, $output, $question);
$input->setArgument('name', $name);
}
// summary and confirmation
$output->writeln(array(
'',
$this->getHelper('formatter')->formatBlock('Summary before generation', 'bg=blue;fg-white', true),
'',
sprintf('You are going to generate a <info>%s</info> command inside <info>%s</info> bundle.', $name, $bundle),
));
$question = new Question($questionHelper->getQuestion('Do you confirm generation', 'yes', '?'), true);
if (!$questionHelper->ask($input, $output, $question)) {
$output->writeln('<error>Command aborted</error>');
return 1;
}
}
public function execute(InputInterface $input, OutputInterface $output)
{
$questionHelper = $this->getQuestionHelper();
$bundle = $input->getArgument('bundle');
$name = $input->getArgument('name');
try {
$bundle = $this->getContainer()->get('kernel')->getBundle($bundle);
} catch (\Exception $e) {
$output->writeln(sprintf('<bg=red>Bundle "%s" does not exist.</>', $bundle));
}
$generator = $this->getGenerator($bundle);
$generator->generate($bundle, $name);
$output->writeln(sprintf('Generated the <info>%s</info> command in <info>%s</info>', $name, $bundle->getName()));
$questionHelper->writeGeneratorSummary($output, array());
}
protected function createGenerator()
{
return new CommandGenerator($this->getContainer()->get('filesystem'));
}
}

View File

@@ -0,0 +1,333 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Sensio\Bundle\GeneratorBundle\Command\Helper\QuestionHelper;
use Sensio\Bundle\GeneratorBundle\Generator\ControllerGenerator;
/**
* Generates controllers.
*
* @author Wouter J <wouter@wouterj.nl>
*/
class GenerateControllerCommand extends GeneratorCommand
{
/**
* @see Command
*/
public function configure()
{
$this
->setName('generate:controller')
->setDescription('Generates a controller')
->setDefinition(array(
new InputOption('controller', '', InputOption::VALUE_REQUIRED, 'The name of the controller to create'),
new InputOption('route-format', '', InputOption::VALUE_REQUIRED, 'The format that is used for the routing (yml, xml, php, annotation)', 'annotation'),
new InputOption('template-format', '', InputOption::VALUE_REQUIRED, 'The format that is used for templating (twig, php)', 'twig'),
new InputOption('actions', '', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'The actions in the controller'),
))
->setHelp(<<<EOT
The <info>%command.name%</info> command helps you generates new controllers
inside bundles.
By default, the command interacts with the developer to tweak the generation.
Any passed option will be used as a default value for the interaction
(<comment>--controller</comment> is the only one needed if you follow the conventions):
<info>php %command.full_name% --controller=AcmeBlogBundle:Post</info>
If you want to disable any user interaction, use <comment>--no-interaction</comment>
but don't forget to pass all needed options:
<info>php %command.full_name% --controller=AcmeBlogBundle:Post --no-interaction</info>
Every generated file is based on a template. There are default templates but they can
be overridden by placing custom templates in one of the following locations, by order of priority:
<info>BUNDLE_PATH/Resources/SensioGeneratorBundle/skeleton/controller
APP_PATH/Resources/SensioGeneratorBundle/skeleton/controller</info>
You can check https://github.com/sensio/SensioGeneratorBundle/tree/master/Resources/skeleton
in order to know the file structure of the skeleton
EOT
)
;
}
public function execute(InputInterface $input, OutputInterface $output)
{
$questionHelper = $this->getQuestionHelper();
if ($input->isInteractive()) {
$question = new ConfirmationQuestion($questionHelper->getQuestion('Do you confirm generation', 'yes', '?'), true);
if (!$questionHelper->ask($input, $output, $question)) {
$output->writeln('<error>Command aborted</error>');
return 1;
}
}
if (null === $input->getOption('controller')) {
throw new \RuntimeException('The controller option must be provided.');
}
list($bundle, $controller) = $this->parseShortcutNotation($input->getOption('controller'));
if (is_string($bundle)) {
$bundle = Validators::validateBundleName($bundle);
try {
$bundle = $this->getContainer()->get('kernel')->getBundle($bundle);
} catch (\Exception $e) {
$output->writeln(sprintf('<bg=red>Bundle "%s" does not exist.</>', $bundle));
}
}
$questionHelper->writeSection($output, 'Controller generation');
$generator = $this->getGenerator($bundle);
$generator->generate($bundle, $controller, $input->getOption('route-format'), $input->getOption('template-format'), $this->parseActions($input->getOption('actions')));
$output->writeln('Generating the bundle code: <info>OK</info>');
$questionHelper->writeGeneratorSummary($output, array());
}
public function interact(InputInterface $input, OutputInterface $output)
{
$questionHelper = $this->getQuestionHelper();
$questionHelper->writeSection($output, 'Welcome to the Symfony2 controller generator');
// namespace
$output->writeln(array(
'',
'Every page, and even sections of a page, are rendered by a <comment>controller</comment>.',
'This command helps you generate them easily.',
'',
'First, you need to give the controller name you want to generate.',
'You must use the shortcut notation like <comment>AcmeBlogBundle:Post</comment>',
'',
));
while (true) {
$question = new Question($questionHelper->getQuestion('Controller name', $input->getOption('controller')), $input->getOption('controller'));
$question->setValidator(array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateControllerName'));
$controller = $questionHelper->ask($input, $output, $question);
list($bundle, $controller) = $this->parseShortcutNotation($controller);
try {
$b = $this->getContainer()->get('kernel')->getBundle($bundle);
if (!file_exists($b->getPath().'/Controller/'.$controller.'Controller.php')) {
break;
}
$output->writeln(sprintf('<bg=red>Controller "%s:%s" already exists.</>', $bundle, $controller));
} catch (\Exception $e) {
$output->writeln(sprintf('<bg=red>Bundle "%s" does not exist.</>', $bundle));
}
}
$input->setOption('controller', $bundle.':'.$controller);
// routing format
$defaultFormat = (null !== $input->getOption('route-format') ? $input->getOption('route-format') : 'annotation');
$output->writeln(array(
'',
'Determine the format to use for the routing.',
'',
));
$question = new Question($questionHelper->getQuestion('Routing format (php, xml, yml, annotation)', $defaultFormat), $defaultFormat);
$question->setValidator(array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateFormat'));
$routeFormat = $questionHelper->ask($input, $output, $question);
$input->setOption('route-format', $routeFormat);
// templating format
$validateTemplateFormat = function ($format) {
if (!in_array($format, array('twig', 'php'))) {
throw new \InvalidArgumentException(sprintf('The template format must be twig or php, "%s" given', $format));
}
return $format;
};
$defaultFormat = (null !== $input->getOption('template-format') ? $input->getOption('template-format') : 'twig');
$output->writeln(array(
'',
'Determine the format to use for templating.',
'',
));
$question = new Question($questionHelper->getQuestion('Template format (twig, php)', $defaultFormat), $defaultFormat);
$question->setValidator($validateTemplateFormat);
$templateFormat = $questionHelper->ask($input, $output, $question);
$input->setOption('template-format', $templateFormat);
// actions
$input->setOption('actions', $this->addActions($input, $output, $questionHelper));
// summary
$output->writeln(array(
'',
$this->getHelper('formatter')->formatBlock('Summary before generation', 'bg=blue;fg-white', true),
'',
sprintf('You are going to generate a "<info>%s:%s</info>" controller', $bundle, $controller),
sprintf('using the "<info>%s</info>" format for the routing and the "<info>%s</info>" format', $routeFormat, $templateFormat),
'for templating',
));
}
public function addActions(InputInterface $input, OutputInterface $output, QuestionHelper $questionHelper)
{
$output->writeln(array(
'',
'Instead of starting with a blank controller, you can add some actions now. An action',
'is a PHP function or method that executes, for example, when a given route is matched.',
'Actions should be suffixed by <comment>Action</comment>.',
'',
));
$templateNameValidator = function ($name) {
if ('default' == $name) {
return $name;
}
if (2 != substr_count($name, ':')) {
throw new \InvalidArgumentException(sprintf('Template name "%s" does not have 2 colons', $name));
}
return $name;
};
$actions = $this->parseActions($input->getOption('actions'));
while (true) {
// name
$output->writeln('');
$question = new Question($questionHelper->getQuestion('New action name (press <return> to stop adding actions)', null), null);
$question->setValidator(function ($name) use ($actions) {
if (null == $name) {
return $name;
}
if (isset($actions[$name])) {
throw new \InvalidArgumentException(sprintf('Action "%s" is already defined', $name));
}
if ('Action' != substr($name, -6)) {
throw new \InvalidArgumentException(sprintf('Name "%s" is not suffixed by Action', $name));
}
return $name;
});
$actionName = $questionHelper->ask($input, $output, $question);
if (!$actionName) {
break;
}
// route
$question = new Question($questionHelper->getQuestion('Action route', '/'.substr($actionName, 0, -6)), '/'.substr($actionName, 0, -6));
$route = $questionHelper->ask($input, $output, $question);
$placeholders = $this->getPlaceholdersFromRoute($route);
// template
$defaultTemplate = $input->getOption('controller').':'.
strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), strtr(substr($actionName, 0, -6), '_', '.')))
.'.html.'.$input->getOption('template-format');
$question = new Question($questionHelper->getQuestion('Template name (optional)', $defaultTemplate), $defaultTemplate);
$template = $questionHelper->ask($input, $output, $question);
// adding action
$actions[$actionName] = array(
'name' => $actionName,
'route' => $route,
'placeholders' => $placeholders,
'template' => $template,
);
}
return $actions;
}
public function parseActions($actions)
{
if (empty($actions) || $actions !== array_values($actions)) {
return $actions;
}
// '$actions' can be an array with just 1 element defining several actions
// separated by white spaces: $actions = array('... ... ...');
if (1 === count($actions)) {
$actions = explode(' ', $actions[0]);
}
$parsedActions = array();
foreach ($actions as $action) {
$data = explode(':', $action);
// name
if (!isset($data[0])) {
throw new \InvalidArgumentException('An action must have a name');
}
$name = array_shift($data);
// route
$route = (isset($data[0]) && '' != $data[0]) ? array_shift($data) : '/'.substr($name, 0, -6);
if ($route) {
$placeholders = $this->getPlaceholdersFromRoute($route);
} else {
$placeholders = array();
}
// template
$template = (0 < count($data) && '' != $data[0]) ? implode(':', $data) : 'default';
$parsedActions[$name] = array(
'name' => $name,
'route' => $route,
'placeholders' => $placeholders,
'template' => $template,
);
}
return $parsedActions;
}
public function getPlaceholdersFromRoute($route)
{
preg_match_all('/{(.*?)}/', $route, $placeholders);
$placeholders = $placeholders[1];
return $placeholders;
}
public function parseShortcutNotation($shortcut)
{
$entity = str_replace('/', '\\', $shortcut);
if (false === $pos = strpos($entity, ':')) {
throw new \InvalidArgumentException(sprintf('The controller name must contain a : ("%s" given, expecting something like AcmeBlogBundle:Post)', $entity));
}
return array(substr($entity, 0, $pos), substr($entity, $pos + 1));
}
protected function createGenerator()
{
return new ControllerGenerator($this->getContainer()->get('filesystem'));
}
}

View File

@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Command;
use Doctrine\Bundle\DoctrineBundle\Mapping\DisconnectedMetadataFactory;
abstract class GenerateDoctrineCommand extends GeneratorCommand
{
public function isEnabled()
{
return class_exists('Doctrine\\Bundle\\DoctrineBundle\\DoctrineBundle');
}
protected function parseShortcutNotation($shortcut)
{
$entity = str_replace('/', '\\', $shortcut);
if (false === $pos = strpos($entity, ':')) {
throw new \InvalidArgumentException(sprintf('The entity name must contain a : ("%s" given, expecting something like AcmeBlogBundle:Blog/Post)', $entity));
}
return array(substr($entity, 0, $pos), substr($entity, $pos + 1));
}
protected function getEntityMetadata($entity)
{
$factory = new DisconnectedMetadataFactory($this->getContainer()->get('doctrine'));
return $factory->getClassMetadata($entity)->getMetadata();
}
}

View File

@@ -0,0 +1,346 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Sensio\Bundle\GeneratorBundle\Command\AutoComplete\EntitiesAutoCompleter;
use Sensio\Bundle\GeneratorBundle\Command\Helper\QuestionHelper;
use Sensio\Bundle\GeneratorBundle\Generator\DoctrineCrudGenerator;
use Sensio\Bundle\GeneratorBundle\Generator\DoctrineFormGenerator;
use Sensio\Bundle\GeneratorBundle\Manipulator\RoutingManipulator;
/**
* Generates a CRUD for a Doctrine entity.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class GenerateDoctrineCrudCommand extends GenerateDoctrineCommand
{
private $formGenerator;
/**
* @see Command
*/
protected function configure()
{
$this
->setName('doctrine:generate:crud')
->setAliases(array('generate:doctrine:crud'))
->setDescription('Generates a CRUD based on a Doctrine entity')
->setDefinition(array(
new InputArgument('entity', InputArgument::OPTIONAL, 'The entity class name to initialize (shortcut notation)'),
new InputOption('entity', '', InputOption::VALUE_REQUIRED, 'The entity class name to initialize (shortcut notation)'),
new InputOption('route-prefix', '', InputOption::VALUE_REQUIRED, 'The route prefix'),
new InputOption('with-write', '', InputOption::VALUE_NONE, 'Whether or not to generate create, new and delete actions'),
new InputOption('format', '', InputOption::VALUE_REQUIRED, 'The format used for configuration files (php, xml, yml, or annotation)', 'annotation'),
new InputOption('overwrite', '', InputOption::VALUE_NONE, 'Overwrite any existing controller or form class when generating the CRUD contents'),
))
->setHelp(<<<EOT
The <info>%command.name%</info> command generates a CRUD based on a Doctrine entity.
The default command only generates the list and show actions.
<info>php %command.full_name% --entity=AcmeBlogBundle:Post --route-prefix=post_admin</info>
Using the --with-write option allows to generate the new, edit and delete actions.
<info>php %command.full_name% doctrine:generate:crud --entity=AcmeBlogBundle:Post --route-prefix=post_admin --with-write</info>
Every generated file is based on a template. There are default templates but they can be overridden by placing custom templates in one of the following locations, by order of priority:
<info>BUNDLE_PATH/Resources/SensioGeneratorBundle/skeleton/crud
APP_PATH/Resources/SensioGeneratorBundle/skeleton/crud</info>
And
<info>__bundle_path__/Resources/SensioGeneratorBundle/skeleton/form
__project_root__/app/Resources/SensioGeneratorBundle/skeleton/form</info>
You can check https://github.com/sensio/SensioGeneratorBundle/tree/master/Resources/skeleton
in order to know the file structure of the skeleton
EOT
)
;
}
/**
* @see Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$questionHelper = $this->getQuestionHelper();
if ($input->isInteractive()) {
$question = new ConfirmationQuestion($questionHelper->getQuestion('Do you confirm generation', 'yes', '?'), true);
if (!$questionHelper->ask($input, $output, $question)) {
$output->writeln('<error>Command aborted</error>');
return 1;
}
}
$entity = Validators::validateEntityName($input->getOption('entity'));
list($bundle, $entity) = $this->parseShortcutNotation($entity);
$format = Validators::validateFormat($input->getOption('format'));
$prefix = $this->getRoutePrefix($input, $entity);
$withWrite = $input->getOption('with-write');
$forceOverwrite = $input->getOption('overwrite');
$questionHelper->writeSection($output, 'CRUD generation');
try {
$entityClass = $this->getContainer()->get('doctrine')->getAliasNamespace($bundle).'\\'.$entity;
$metadata = $this->getEntityMetadata($entityClass);
} catch (\Exception $e) {
throw new \RuntimeException(sprintf('Entity "%s" does not exist in the "%s" bundle. Create it with the "doctrine:generate:entity" command and then execute this command again.', $entity, $bundle));
}
$bundle = $this->getContainer()->get('kernel')->getBundle($bundle);
$generator = $this->getGenerator($bundle);
$generator->generate($bundle, $entity, $metadata[0], $format, $prefix, $withWrite, $forceOverwrite);
$output->writeln('Generating the CRUD code: <info>OK</info>');
$errors = array();
$runner = $questionHelper->getRunner($output, $errors);
// form
if ($withWrite) {
$this->generateForm($bundle, $entity, $metadata, $forceOverwrite);
$output->writeln('Generating the Form code: <info>OK</info>');
}
// routing
$output->write('Updating the routing: ');
if ('annotation' != $format) {
$runner($this->updateRouting($questionHelper, $input, $output, $bundle, $format, $entity, $prefix));
} else {
$runner($this->updateAnnotationRouting($bundle, $entity, $prefix));
}
$questionHelper->writeGeneratorSummary($output, $errors);
}
protected function interact(InputInterface $input, OutputInterface $output)
{
$questionHelper = $this->getQuestionHelper();
$questionHelper->writeSection($output, 'Welcome to the Doctrine2 CRUD generator');
// namespace
$output->writeln(array(
'',
'This command helps you generate CRUD controllers and templates.',
'',
'First, give the name of the existing entity for which you want to generate a CRUD',
'(use the shortcut notation like <comment>AcmeBlogBundle:Post</comment>)',
'',
));
if ($input->hasArgument('entity') && $input->getArgument('entity') != '') {
$input->setOption('entity', $input->getArgument('entity'));
}
$question = new Question($questionHelper->getQuestion('The Entity shortcut name', $input->getOption('entity')), $input->getOption('entity'));
$question->setValidator(array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateEntityName'));
$autocompleter = new EntitiesAutoCompleter($this->getContainer()->get('doctrine')->getManager());
$autocompleteEntities = $autocompleter->getSuggestions();
$question->setAutocompleterValues($autocompleteEntities);
$entity = $questionHelper->ask($input, $output, $question);
$input->setOption('entity', $entity);
list($bundle, $entity) = $this->parseShortcutNotation($entity);
try {
$entityClass = $this->getContainer()->get('doctrine')->getAliasNamespace($bundle).'\\'.$entity;
$metadata = $this->getEntityMetadata($entityClass);
} catch (\Exception $e) {
throw new \RuntimeException(sprintf('Entity "%s" does not exist in the "%s" bundle. You may have mistyped the bundle name or maybe the entity doesn\'t exist yet (create it first with the "doctrine:generate:entity" command).', $entity, $bundle));
}
// write?
$withWrite = $input->getOption('with-write') ?: false;
$output->writeln(array(
'',
'By default, the generator creates two actions: list and show.',
'You can also ask it to generate "write" actions: new, update, and delete.',
'',
));
$question = new ConfirmationQuestion($questionHelper->getQuestion('Do you want to generate the "write" actions', $withWrite ? 'yes' : 'no', '?', $withWrite), $withWrite);
$withWrite = $questionHelper->ask($input, $output, $question);
$input->setOption('with-write', $withWrite);
// format
$format = $input->getOption('format');
$output->writeln(array(
'',
'Determine the format to use for the generated CRUD.',
'',
));
$question = new Question($questionHelper->getQuestion('Configuration format (yml, xml, php, or annotation)', $format), $format);
$question->setValidator(array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateFormat'));
$format = $questionHelper->ask($input, $output, $question);
$input->setOption('format', $format);
// route prefix
$prefix = $this->getRoutePrefix($input, $entity);
$output->writeln(array(
'',
'Determine the routes prefix (all the routes will be "mounted" under this',
'prefix: /prefix/, /prefix/new, ...).',
'',
));
$prefix = $questionHelper->ask($input, $output, new Question($questionHelper->getQuestion('Routes prefix', '/'.$prefix), '/'.$prefix));
$input->setOption('route-prefix', $prefix);
// summary
$output->writeln(array(
'',
$this->getHelper('formatter')->formatBlock('Summary before generation', 'bg=blue;fg=white', true),
'',
sprintf('You are going to generate a CRUD controller for "<info>%s:%s</info>"', $bundle, $entity),
sprintf('using the "<info>%s</info>" format.', $format),
'',
));
}
/**
* Tries to generate forms if they don't exist yet and if we need write operations on entities.
*/
protected function generateForm($bundle, $entity, $metadata, $forceOverwrite = false)
{
$this->getFormGenerator($bundle)->generate($bundle, $entity, $metadata[0], $forceOverwrite);
}
protected function updateRouting(QuestionHelper $questionHelper, InputInterface $input, OutputInterface $output, BundleInterface $bundle, $format, $entity, $prefix)
{
$auto = true;
if ($input->isInteractive()) {
$question = new ConfirmationQuestion($questionHelper->getQuestion('Confirm automatic update of the Routing', 'yes', '?'), true);
$auto = $questionHelper->ask($input, $output, $question);
}
$output->write('Importing the CRUD routes: ');
$this->getContainer()->get('filesystem')->mkdir($bundle->getPath().'/Resources/config/');
// first, import the routing file from the bundle's main routing.yml file
$routing = new RoutingManipulator($bundle->getPath().'/Resources/config/routing.yml');
try {
$ret = $auto ? $routing->addResource($bundle->getName(), $format, '/'.$prefix, 'routing/'.strtolower(str_replace('\\', '_', $entity))) : false;
} catch (\RuntimeException $exc) {
$ret = false;
}
if (!$ret) {
$help = sprintf(" <comment>resource: \"@%s/Resources/config/routing/%s.%s\"</comment>\n", $bundle->getName(), strtolower(str_replace('\\', '_', $entity)), $format);
$help .= sprintf(" <comment>prefix: /%s</comment>\n", $prefix);
return array(
'- Import the bundle\'s routing resource in the bundle routing file',
sprintf(' (%s).', $bundle->getPath().'/Resources/config/routing.yml'),
'',
sprintf(' <comment>%s:</comment>', $routing->getImportedResourceYamlKey($bundle->getName(), $prefix)),
$help,
'',
);
}
// second, import the bundle's routing.yml file from the application's routing.yml file
$routing = new RoutingManipulator($this->getContainer()->getParameter('kernel.root_dir').'/config/routing.yml');
try {
$ret = $auto ? $routing->addResource($bundle->getName(), 'yml') : false;
} catch (\RuntimeException $e) {
// the bundle is already imported form app's routing.yml file
$errorMessage = sprintf(
"\n\n[ERROR] The bundle's \"Resources/config/routing.yml\" file cannot be imported\n".
"from \"app/config/routing.yml\" because the \"%s\" bundle is\n".
"already imported. Make sure you are not using two different\n".
"configuration/routing formats in the same bundle because it won't work.\n",
$bundle->getName()
);
$output->write($errorMessage);
$ret = true;
} catch (\Exception $e) {
$ret = false;
}
if (!$ret) {
return array(
'- Import the bundle\'s routing.yml file in the application routing.yml file',
sprintf('# app/config/routing.yml'),
sprintf('%s:', $bundle->getName()),
sprintf(' <comment>resource: "@%s/Resources/config/routing.yml"</comment>', $bundle->getName()),
'',
'# ...',
'',
);
}
}
protected function updateAnnotationRouting(BundleInterface $bundle, $entity, $prefix)
{
$rootDir = $this->getContainer()->getParameter('kernel.root_dir');
$routing = new RoutingManipulator($rootDir.'/config/routing.yml');
if (!$routing->hasResourceInAnnotation($bundle->getName())) {
$parts = explode('\\', $entity);
$controller = array_pop($parts);
$ret = $routing->addAnnotationController($bundle->getName(), $controller);
}
}
protected function getRoutePrefix(InputInterface $input, $entity)
{
$prefix = $input->getOption('route-prefix') ?: strtolower(str_replace(array('\\', '/'), '_', $entity));
if ($prefix && '/' === $prefix[0]) {
$prefix = substr($prefix, 1);
}
return $prefix;
}
protected function createGenerator($bundle = null)
{
return new DoctrineCrudGenerator(
$this->getContainer()->get('filesystem'),
$this->getContainer()->getParameter('kernel.root_dir')
);
}
protected function getFormGenerator($bundle = null)
{
if (null === $this->formGenerator) {
$this->formGenerator = new DoctrineFormGenerator($this->getContainer()->get('filesystem'));
$this->formGenerator->setSkeletonDirs($this->getSkeletonDirs($bundle));
}
return $this->formGenerator;
}
public function setFormGenerator(DoctrineFormGenerator $formGenerator)
{
$this->formGenerator = $formGenerator;
}
}

View File

@@ -0,0 +1,399 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Command;
use Sensio\Bundle\GeneratorBundle\Generator\DoctrineEntityGenerator;
use Sensio\Bundle\GeneratorBundle\Command\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\Console\Question\Question;
use Doctrine\DBAL\Types\Type;
/**
* Initializes a Doctrine entity inside a bundle.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class GenerateDoctrineEntityCommand extends GenerateDoctrineCommand
{
protected function configure()
{
$this
->setName('doctrine:generate:entity')
->setAliases(array('generate:doctrine:entity'))
->setDescription('Generates a new Doctrine entity inside a bundle')
->addOption('entity', null, InputOption::VALUE_REQUIRED, 'The entity class name to initialize (shortcut notation)')
->addOption('fields', null, InputOption::VALUE_REQUIRED, 'The fields to create with the new entity')
->addOption('format', null, InputOption::VALUE_REQUIRED, 'Use the format for configuration files (php, xml, yml, or annotation)', 'annotation')
->setHelp(<<<EOT
The <info>%command.name%</info> task generates a new Doctrine
entity inside a bundle:
<info>php %command.full_name% --entity=AcmeBlogBundle:Blog/Post</info>
The above command would initialize a new entity in the following entity
namespace <info>Acme\BlogBundle\Entity\Blog\Post</info>.
You can also optionally specify the fields you want to generate in the new
entity:
<info>php %command.full_name% --entity=AcmeBlogBundle:Blog/Post --fields="title:string(255) body:text"</info>
By default, the command uses annotations for the mapping information; change it
with <comment>--format</comment>:
<info>php %command.full_name% --entity=AcmeBlogBundle:Blog/Post --format=yml</info>
To deactivate the interaction mode, simply use the <comment>--no-interaction</comment> option
without forgetting to pass all needed options:
<info>php %command.full_name% --entity=AcmeBlogBundle:Blog/Post --format=annotation --fields="title:string(255) body:text" --no-interaction</info>
This also has support for passing field specific attributes:
<info>php %command.full_name% --entity=AcmeBlogBundle:Blog/Post --format=annotation --fields="title:string(length=255 nullable=true unique=true) body:text ranking:decimal(precision:10 scale:0)" --no-interaction</info>
EOT
);
}
/**
* @throws \InvalidArgumentException When the bundle doesn't end with Bundle (Example: "Bundle/MySampleBundle")
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$questionHelper = $this->getQuestionHelper();
$entity = Validators::validateEntityName($input->getOption('entity'));
list($bundle, $entity) = $this->parseShortcutNotation($entity);
$format = Validators::validateFormat($input->getOption('format'));
$fields = $this->parseFields($input->getOption('fields'));
$questionHelper->writeSection($output, 'Entity generation');
$bundle = $this->getContainer()->get('kernel')->getBundle($bundle);
/** @var DoctrineEntityGenerator $generator */
$generator = $this->getGenerator();
$generatorResult = $generator->generate($bundle, $entity, $format, array_values($fields));
$output->writeln(sprintf(
'> Generating entity class <info>%s</info>: <comment>OK!</comment>',
$this->makePathRelative($generatorResult->getEntityPath())
));
$output->writeln(sprintf(
'> Generating repository class <info>%s</info>: <comment>OK!</comment>',
$this->makePathRelative($generatorResult->getRepositoryPath())
));
if ($generatorResult->getMappingPath()) {
$output->writeln(sprintf(
'> Generating mapping file <info>%s</info>: <comment>OK!</comment>',
$this->makePathRelative($generatorResult->getMappingPath())
));
}
$questionHelper->writeGeneratorSummary($output, array());
}
protected function interact(InputInterface $input, OutputInterface $output)
{
$questionHelper = $this->getQuestionHelper();
$questionHelper->writeSection($output, 'Welcome to the Doctrine2 entity generator');
// namespace
$output->writeln(array(
'',
'This command helps you generate Doctrine2 entities.',
'',
'First, you need to give the entity name you want to generate.',
'You must use the shortcut notation like <comment>AcmeBlogBundle:Post</comment>.',
'',
));
$bundleNames = array_keys($this->getContainer()->get('kernel')->getBundles());
while (true) {
$question = new Question($questionHelper->getQuestion('The Entity shortcut name', $input->getOption('entity')), $input->getOption('entity'));
$question->setValidator(array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateEntityName'));
$question->setAutocompleterValues($bundleNames);
$entity = $questionHelper->ask($input, $output, $question);
list($bundle, $entity) = $this->parseShortcutNotation($entity);
// check reserved words
if ($this->getGenerator()->isReservedKeyword($entity)) {
$output->writeln(sprintf('<bg=red> "%s" is a reserved word</>.', $entity));
continue;
}
try {
$b = $this->getContainer()->get('kernel')->getBundle($bundle);
if (!file_exists($b->getPath().'/Entity/'.str_replace('\\', '/', $entity).'.php')) {
break;
}
$output->writeln(sprintf('<bg=red>Entity "%s:%s" already exists</>.', $bundle, $entity));
} catch (\Exception $e) {
$output->writeln(sprintf('<bg=red>Bundle "%s" does not exist.</>', $bundle));
}
}
$input->setOption('entity', $bundle.':'.$entity);
// format
$output->writeln(array(
'',
'Determine the format to use for the mapping information.',
'',
));
$formats = array('yml', 'xml', 'php', 'annotation');
$question = new Question($questionHelper->getQuestion('Configuration format (yml, xml, php, or annotation)', $input->getOption('format')), $input->getOption('format'));
$question->setValidator(array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateFormat'));
$question->setAutocompleterValues($formats);
$format = $questionHelper->ask($input, $output, $question);
$input->setOption('format', $format);
// fields
$input->setOption('fields', $this->addFields($input, $output, $questionHelper));
}
private function parseFields($input)
{
if (is_array($input)) {
return $input;
}
$fields = array();
foreach (preg_split('{(?:\([^\(]*\))(*SKIP)(*F)|\s+}', $input) as $value) {
$elements = explode(':', $value);
$name = $elements[0];
$fieldAttributes = array();
if (strlen($name)) {
$fieldAttributes['fieldName'] = $name;
$type = isset($elements[1]) ? $elements[1] : 'string';
preg_match_all('{(.*)\((.*)\)}', $type, $matches);
$fieldAttributes['type'] = isset($matches[1][0]) ? $matches[1][0] : $type;
$length = null;
if ('string' === $fieldAttributes['type']) {
$fieldAttributes['length'] = $length;
}
if (isset($matches[2][0]) && $length = $matches[2][0]) {
$attributesFound = array();
if (false !== strpos($length, '=')) {
preg_match_all('{([^,= ]+)=([^,= ]+)}', $length, $result);
$attributesFound = array_combine($result[1], $result[2]);
} else {
$fieldAttributes['length'] = $length;
}
$fieldAttributes = array_merge($fieldAttributes, $attributesFound);
foreach (array('length', 'precision', 'scale') as $intAttribute) {
if (isset($fieldAttributes[$intAttribute])) {
$fieldAttributes[$intAttribute] = (int) $fieldAttributes[$intAttribute];
}
}
foreach (array('nullable', 'unique') as $boolAttribute) {
if (isset($fieldAttributes[$boolAttribute])) {
$fieldAttributes[$boolAttribute] = (bool) $fieldAttributes[$boolAttribute];
}
}
}
$fields[$name] = $fieldAttributes;
}
}
return $fields;
}
private function addFields(InputInterface $input, OutputInterface $output, QuestionHelper $questionHelper)
{
$fields = $this->parseFields($input->getOption('fields'));
$output->writeln(array(
'',
'Instead of starting with a blank entity, you can add some fields now.',
'Note that the primary key will be added automatically (named <comment>id</comment>).',
'',
));
$output->write('<info>Available types:</info> ');
$types = array_keys(Type::getTypesMap());
$count = 20;
foreach ($types as $i => $type) {
if ($count > 50) {
$count = 0;
$output->writeln('');
}
$count += strlen($type);
$output->write(sprintf('<comment>%s</comment>', $type));
if (count($types) != $i + 1) {
$output->write(', ');
} else {
$output->write('.');
}
}
$output->writeln('');
$fieldValidator = function ($type) use ($types) {
if (!in_array($type, $types)) {
throw new \InvalidArgumentException(sprintf('Invalid type "%s".', $type));
}
return $type;
};
$lengthValidator = function ($length) {
if (!$length) {
return $length;
}
$result = filter_var($length, FILTER_VALIDATE_INT, array(
'options' => array('min_range' => 1),
));
if (false === $result) {
throw new \InvalidArgumentException(sprintf('Invalid length "%s".', $length));
}
return $length;
};
$boolValidator = function ($value) {
if (null === $valueAsBool = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)) {
throw new \InvalidArgumentException(sprintf('Invalid bool value "%s".', $value));
}
return $valueAsBool;
};
$precisionValidator = function ($precision) {
if (!$precision) {
return $precision;
}
$result = filter_var($precision, FILTER_VALIDATE_INT, array(
'options' => array('min_range' => 1, 'max_range' => 65),
));
if (false === $result) {
throw new \InvalidArgumentException(sprintf('Invalid precision "%s".', $precision));
}
return $precision;
};
$scaleValidator = function ($scale) {
if (!$scale) {
return $scale;
}
$result = filter_var($scale, FILTER_VALIDATE_INT, array(
'options' => array('min_range' => 0, 'max_range' => 30),
));
if (false === $result) {
throw new \InvalidArgumentException(sprintf('Invalid scale "%s".', $scale));
}
return $scale;
};
while (true) {
$output->writeln('');
$generator = $this->getGenerator();
$question = new Question($questionHelper->getQuestion('New field name (press <return> to stop adding fields)', null), null);
$question->setValidator(function ($name) use ($fields, $generator) {
if (isset($fields[$name]) || 'id' == $name) {
throw new \InvalidArgumentException(sprintf('Field "%s" is already defined.', $name));
}
// check reserved words
if ($generator->isReservedKeyword($name)) {
throw new \InvalidArgumentException(sprintf('Name "%s" is a reserved word.', $name));
}
// check for valid PHP variable name
if (!is_null($name) && !$generator->isValidPhpVariableName($name)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid PHP variable name.', $name));
}
return $name;
});
$columnName = $questionHelper->ask($input, $output, $question);
if (!$columnName) {
break;
}
$defaultType = 'string';
// try to guess the type by the column name prefix/suffix
if (substr($columnName, -3) == '_at') {
$defaultType = 'datetime';
} elseif (substr($columnName, -3) == '_id') {
$defaultType = 'integer';
} elseif (substr($columnName, 0, 3) == 'is_') {
$defaultType = 'boolean';
} elseif (substr($columnName, 0, 4) == 'has_') {
$defaultType = 'boolean';
}
$question = new Question($questionHelper->getQuestion('Field type', $defaultType), $defaultType);
$question->setValidator($fieldValidator);
$question->setAutocompleterValues($types);
$type = $questionHelper->ask($input, $output, $question);
$data = array('columnName' => $columnName, 'fieldName' => lcfirst(Container::camelize($columnName)), 'type' => $type);
if ($type == 'string') {
$question = new Question($questionHelper->getQuestion('Field length', 255), 255);
$question->setValidator($lengthValidator);
$data['length'] = $questionHelper->ask($input, $output, $question);
} elseif ('decimal' === $type) {
// 10 is the default value given in \Doctrine\DBAL\Schema\Column::$_precision
$question = new Question($questionHelper->getQuestion('Precision', 10), 10);
$question->setValidator($precisionValidator);
$data['precision'] = $questionHelper->ask($input, $output, $question);
// 0 is the default value given in \Doctrine\DBAL\Schema\Column::$_scale
$question = new Question($questionHelper->getQuestion('Scale', 0), 0);
$question->setValidator($scaleValidator);
$data['scale'] = $questionHelper->ask($input, $output, $question);
}
$question = new Question($questionHelper->getQuestion('Is nullable', 'false'), false);
$question->setValidator($boolValidator);
$question->setAutocompleterValues(array('true', 'false'));
if ($nullable = $questionHelper->ask($input, $output, $question)) {
$data['nullable'] = $nullable;
}
$question = new Question($questionHelper->getQuestion('Unique', 'false'), false);
$question->setValidator($boolValidator);
$question->setAutocompleterValues(array('true', 'false'));
if ($unique = $questionHelper->ask($input, $output, $question)) {
$data['unique'] = $unique;
}
$fields[$columnName] = $data;
}
return $fields;
}
protected function createGenerator()
{
return new DoctrineEntityGenerator($this->getContainer()->get('filesystem'), $this->getContainer()->get('doctrine'));
}
}

View File

@@ -0,0 +1,83 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;
use Sensio\Bundle\GeneratorBundle\Generator\DoctrineFormGenerator;
/**
* Generates a form type class for a given Doctrine entity.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Hugo Hamon <hugo.hamon@sensio.com>
*/
class GenerateDoctrineFormCommand extends GenerateDoctrineCommand
{
/**
* @see Command
*/
protected function configure()
{
$this
->setName('doctrine:generate:form')
->setAliases(array('generate:doctrine:form'))
->setDescription('Generates a form type class based on a Doctrine entity')
->setDefinition(array(
new InputArgument('entity', InputArgument::REQUIRED, 'The entity class name to initialize (shortcut notation)'),
))
->setHelp(<<<EOT
The <info>%command.name%</info> command generates a form class based on a Doctrine entity.
<info>php %command.full_name% AcmeBlogBundle:Post</info>
Every generated file is based on a template. There are default templates but they can be overriden by placing custom templates in one of the following locations, by order of priority:
<info>BUNDLE_PATH/Resources/SensioGeneratorBundle/skeleton/form
APP_PATH/Resources/SensioGeneratorBundle/skeleton/form</info>
You can check https://github.com/sensio/SensioGeneratorBundle/tree/master/Resources/skeleton
in order to know the file structure of the skeleton
EOT
)
;
}
/**
* @see Command
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$entity = Validators::validateEntityName($input->getArgument('entity'));
list($bundle, $entity) = $this->parseShortcutNotation($entity);
$entityClass = $this->getContainer()->get('doctrine')->getAliasNamespace($bundle).'\\'.$entity;
$metadata = $this->getEntityMetadata($entityClass);
$bundle = $this->getApplication()->getKernel()->getBundle($bundle);
$generator = $this->getGenerator($bundle);
$generator->generate($bundle, $entity, $metadata[0]);
$output->writeln(sprintf(
'The new %s.php class file has been created under %s.',
$generator->getClassName(),
$generator->getClassPath()
));
}
protected function createGenerator()
{
return new DoctrineFormGenerator($this->getContainer()->get('filesystem'));
}
}

View File

@@ -0,0 +1,87 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Command;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Sensio\Bundle\GeneratorBundle\Generator\Generator;
use Sensio\Bundle\GeneratorBundle\Command\Helper\QuestionHelper;
/**
* Base class for generator commands.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class GeneratorCommand extends ContainerAwareCommand
{
private $generator;
// only useful for unit tests
public function setGenerator(Generator $generator)
{
$this->generator = $generator;
}
abstract protected function createGenerator();
protected function getGenerator(BundleInterface $bundle = null)
{
if (null === $this->generator) {
$this->generator = $this->createGenerator();
$this->generator->setSkeletonDirs($this->getSkeletonDirs($bundle));
}
return $this->generator;
}
protected function getSkeletonDirs(BundleInterface $bundle = null)
{
$skeletonDirs = array();
if (isset($bundle) && is_dir($dir = $bundle->getPath().'/Resources/SensioGeneratorBundle/skeleton')) {
$skeletonDirs[] = $dir;
}
if (is_dir($dir = $this->getContainer()->get('kernel')->getRootdir().'/Resources/SensioGeneratorBundle/skeleton')) {
$skeletonDirs[] = $dir;
}
$skeletonDirs[] = __DIR__.'/../Resources/skeleton';
$skeletonDirs[] = __DIR__.'/../Resources';
return $skeletonDirs;
}
protected function getQuestionHelper()
{
$question = $this->getHelperSet()->get('question');
if (!$question || get_class($question) !== 'Sensio\Bundle\GeneratorBundle\Command\Helper\QuestionHelper') {
$this->getHelperSet()->set($question = new QuestionHelper());
}
return $question;
}
/**
* Tries to make a path relative to the project, which prints nicer.
*
* @param string $absolutePath
*
* @return string
*/
protected function makePathRelative($absolutePath)
{
$projectRootDir = dirname($this->getContainer()->getParameter('kernel.root_dir'));
return str_replace($projectRootDir.'/', '', realpath($absolutePath) ?: $absolutePath);
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Command\Helper;
use Symfony\Component\Console\Helper\QuestionHelper as BaseQuestionHelper;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Generates bundles.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class QuestionHelper extends BaseQuestionHelper
{
public function writeGeneratorSummary(OutputInterface $output, $errors)
{
if (!$errors) {
$this->writeSection($output, 'Everything is OK! Now get to work :).');
} else {
$this->writeSection($output, array(
'The command was not able to configure everything automatically.',
'You\'ll need to make the following changes manually.',
), 'error');
$output->writeln($errors);
}
}
public function getRunner(OutputInterface $output, &$errors)
{
$runner = function ($err) use ($output, &$errors) {
if ($err) {
$output->writeln('<fg=red>FAILED</>');
$errors = array_merge($errors, $err);
} else {
$output->writeln('<info>OK</info>');
}
};
return $runner;
}
public function writeSection(OutputInterface $output, $text, $style = 'bg=blue;fg=white')
{
$output->writeln(array(
'',
$this->getHelperSet()->get('formatter')->formatBlock($text, $style, true),
'',
));
}
public function getQuestion($question, $default, $sep = ':')
{
return $default ? sprintf('<info>%s</info> [<comment>%s</comment>]%s ', $question, $default, $sep) : sprintf('<info>%s</info>%s ', $question, $sep);
}
}

View File

@@ -0,0 +1,210 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Command;
/**
* Validator functions.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Validators
{
/**
* Validates that the given namespace (e.g. Acme\FooBundle) is a valid format.
*
* If $requireVendorNamespace is true, then we require you to have a vendor
* namespace (e.g. Acme).
*
* @param $namespace
* @param bool $requireVendorNamespace
*
* @return string
*/
public static function validateBundleNamespace($namespace, $requireVendorNamespace = true)
{
if (!preg_match('/Bundle$/', $namespace)) {
throw new \InvalidArgumentException('The namespace must end with Bundle.');
}
$namespace = strtr($namespace, '/', '\\');
if (!preg_match('/^(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\\\?)+$/', $namespace)) {
throw new \InvalidArgumentException('The namespace contains invalid characters.');
}
// validate reserved keywords
$reserved = self::getReservedWords();
foreach (explode('\\', $namespace) as $word) {
if (in_array(strtolower($word), $reserved)) {
throw new \InvalidArgumentException(sprintf('The namespace cannot contain PHP reserved words ("%s").', $word));
}
}
// validate that the namespace is at least one level deep
if ($requireVendorNamespace && false === strpos($namespace, '\\')) {
$msg = array();
$msg[] = sprintf('The namespace must contain a vendor namespace (e.g. "VendorName\%s" instead of simply "%s").', $namespace, $namespace);
$msg[] = 'If you\'ve specified a vendor namespace, did you forget to surround it with quotes (init:bundle "Acme\BlogBundle")?';
throw new \InvalidArgumentException(implode("\n\n", $msg));
}
return $namespace;
}
public static function validateBundleName($bundle)
{
if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $bundle)) {
throw new \InvalidArgumentException(sprintf('The bundle name %s contains invalid characters.', $bundle));
}
if (!preg_match('/Bundle$/', $bundle)) {
throw new \InvalidArgumentException('The bundle name must end with Bundle.');
}
return $bundle;
}
public static function validateControllerName($controller)
{
try {
self::validateEntityName($controller);
} catch (\InvalidArgumentException $e) {
throw new \InvalidArgumentException(
sprintf(
'The controller name must contain a : ("%s" given, expecting something like AcmeBlogBundle:Post)',
$controller
)
);
}
return $controller;
}
public static function validateFormat($format)
{
if (!$format) {
throw new \RuntimeException('Please enter a configuration format.');
}
$format = strtolower($format);
// in case they typed "yaml", but ok with that
if ($format == 'yaml') {
$format = 'yml';
}
if (!in_array($format, array('php', 'xml', 'yml', 'annotation'))) {
throw new \RuntimeException(sprintf('Format "%s" is not supported.', $format));
}
return $format;
}
/**
* Performs basic checks in entity name.
*
* @param string $entity
*
* @return string
*
* @throws \InvalidArgumentException
*/
public static function validateEntityName($entity)
{
if (!preg_match('{^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*:[a-zA-Z0-9_\x7f-\xff\\\/]+$}', $entity)) {
throw new \InvalidArgumentException(sprintf('The entity name isn\'t valid ("%s" given, expecting something like AcmeBlogBundle:Blog/Post)', $entity));
}
return $entity;
}
public static function getReservedWords()
{
return array(
'abstract',
'and',
'array',
'as',
'break',
'callable',
'case',
'catch',
'class',
'clone',
'const',
'continue',
'declare',
'default',
'do',
'else',
'elseif',
'enddeclare',
'endfor',
'endforeach',
'endif',
'endswitch',
'endwhile',
'extends',
'final',
'finally',
'for',
'foreach',
'function',
'global',
'goto',
'if',
'implements',
'interface',
'instanceof',
'insteadof',
'namespace',
'new',
'or',
'private',
'protected',
'public',
'static',
'switch',
'throw',
'trait',
'try',
'use',
'var',
'while',
'xor',
'yield',
'__CLASS__',
'__DIR__',
'__FILE__',
'__LINE__',
'__FUNCTION__',
'__METHOD__',
'__NAMESPACE__',
'__TRAIT__',
'__halt_compiler',
'die',
'echo',
'empty',
'exit',
'eval',
'include',
'include_once',
'isset',
'list',
'require',
'require_once',
'return',
'print',
'unset',
);
}
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Generator;
use Sensio\Bundle\GeneratorBundle\Model\Bundle;
use Symfony\Component\Filesystem\Filesystem;
/**
* Generates a bundle.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class BundleGenerator extends Generator
{
private $filesystem;
public function __construct(Filesystem $filesystem)
{
$this->filesystem = $filesystem;
}
public function generateBundle(Bundle $bundle)
{
$dir = $bundle->getTargetDirectory();
if (file_exists($dir)) {
if (!is_dir($dir)) {
throw new \RuntimeException(sprintf('Unable to generate the bundle as the target directory "%s" exists but is a file.', realpath($dir)));
}
$files = scandir($dir);
if ($files != array('.', '..')) {
throw new \RuntimeException(sprintf('Unable to generate the bundle as the target directory "%s" is not empty.', realpath($dir)));
}
if (!is_writable($dir)) {
throw new \RuntimeException(sprintf('Unable to generate the bundle as the target directory "%s" is not writable.', realpath($dir)));
}
}
$parameters = array(
'namespace' => $bundle->getNamespace(),
'bundle' => $bundle->getName(),
'format' => $bundle->getConfigurationFormat(),
'bundle_basename' => $bundle->getBasename(),
'extension_alias' => $bundle->getExtensionAlias(),
);
$this->renderFile('bundle/Bundle.php.twig', $dir.'/'.$bundle->getName().'.php', $parameters);
if ($bundle->shouldGenerateDependencyInjectionDirectory()) {
$this->renderFile('bundle/Extension.php.twig', $dir.'/DependencyInjection/'.$bundle->getBasename().'Extension.php', $parameters);
$this->renderFile('bundle/Configuration.php.twig', $dir.'/DependencyInjection/Configuration.php', $parameters);
}
$this->renderFile('bundle/DefaultController.php.twig', $dir.'/Controller/DefaultController.php', $parameters);
$this->renderFile('bundle/DefaultControllerTest.php.twig', $bundle->getTestsDirectory().'/Controller/DefaultControllerTest.php', $parameters);
$this->renderFile('bundle/index.html.twig.twig', $dir.'/Resources/views/Default/index.html.twig', $parameters);
// render the services.yml/xml file
$servicesFilename = $bundle->getServicesConfigurationFilename();
$this->renderFile(
sprintf('bundle/%s.twig', $servicesFilename),
$dir.'/Resources/config/'.$servicesFilename, $parameters
);
if ($routingFilename = $bundle->getRoutingConfigurationFilename()) {
$this->renderFile(
sprintf('bundle/%s.twig', $routingFilename),
$dir.'/Resources/config/'.$routingFilename, $parameters
);
}
}
}

View File

@@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Generator;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
/**
* Generates a Command inside a bundle.
*
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
class CommandGenerator extends Generator
{
private $filesystem;
/**
* Constructor.
*
* @param Filesystem $filesystem A Filesystem instance
*/
public function __construct(Filesystem $filesystem)
{
$this->filesystem = $filesystem;
}
public function generate(BundleInterface $bundle, $name)
{
$bundleDir = $bundle->getPath();
$commandDir = $bundleDir.'/Command';
$this->filesystem->mkdir($commandDir);
$commandClassName = $this->classify($name).'Command';
$commandFile = $commandDir.'/'.$commandClassName.'.php';
if ($this->filesystem->exists($commandFile)) {
throw new \RuntimeException(sprintf('Command "%s" already exists', $name));
}
$parameters = array(
'namespace' => $bundle->getNamespace(),
'class_name' => $commandClassName,
'name' => $name,
);
$this->renderFile('command/Command.php.twig', $commandFile, $parameters);
}
/**
* Transforms the given string to a new string valid as a PHP class name
* ('app:my-project' -> 'AppMyProject', 'app:namespace:name' -> 'AppNamespaceName').
*
* @param string $string
*
* @return The string transformed to be a valid PHP class name
*/
public function classify($string)
{
return str_replace(' ', '', ucwords(strtr($string, '_-:', ' ')));
}
}

View File

@@ -0,0 +1,201 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Generator;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
/**
* Generates a Controller inside a bundle.
*
* @author Wouter J <wouter@wouterj.nl>
*/
class ControllerGenerator extends Generator
{
private $filesystem;
/**
* Constructor.
*
* @param Filesystem $filesystem A Filesystem instance
*/
public function __construct(Filesystem $filesystem)
{
$this->filesystem = $filesystem;
}
public function generate(BundleInterface $bundle, $controller, $routeFormat, $templateFormat, array $actions = array())
{
$dir = $bundle->getPath();
$controllerFile = $dir.'/Controller/'.$controller.'Controller.php';
if (file_exists($controllerFile)) {
throw new \RuntimeException(sprintf('Controller "%s" already exists', $controller));
}
$parameters = array(
'namespace' => $bundle->getNamespace(),
'bundle' => $bundle->getName(),
'format' => array(
'routing' => $routeFormat,
'templating' => $templateFormat,
),
'controller' => $controller,
);
foreach ($actions as $i => $action) {
// get the action name without the suffix Action (for the template logical name)
$actions[$i]['basename'] = substr($action['name'], 0, -6);
$params = $parameters;
$params['action'] = $actions[$i];
// create a template
$template = $actions[$i]['template'];
if ('default' == $template) {
@trigger_error('The use of the "default" keyword is deprecated. Use the real template name instead.', E_USER_DEPRECATED);
$template = $bundle->getName().':'.$controller.':'.
strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), strtr(substr($actionName, 0, -6), '_', '.')))
.'.html.'.$templateFormat;
}
if ('twig' == $templateFormat) {
$this->renderFile('controller/Template.html.twig.twig', $dir.'/Resources/views/'.$this->parseTemplatePath($template), $params);
} else {
$this->renderFile('controller/Template.html.php.twig', $dir.'/Resources/views/'.$this->parseTemplatePath($template), $params);
}
$this->generateRouting($bundle, $controller, $actions[$i], $routeFormat);
}
$parameters['actions'] = $actions;
$this->renderFile('controller/Controller.php.twig', $controllerFile, $parameters);
$this->renderFile('controller/ControllerTest.php.twig', $dir.'/Tests/Controller/'.$controller.'ControllerTest.php', $parameters);
}
public function generateRouting(BundleInterface $bundle, $controller, array $action, $format)
{
// annotation is generated in the templates
if ('annotation' == $format) {
return true;
}
$file = $bundle->getPath().'/Resources/config/routing.'.$format;
if (file_exists($file)) {
$content = file_get_contents($file);
} elseif (!is_dir($dir = $bundle->getPath().'/Resources/config')) {
mkdir($dir);
}
$controller = $bundle->getName().':'.$controller.':'.$action['basename'];
$name = strtolower(preg_replace('/([A-Z])/', '_\\1', $action['basename']));
if ('yml' == $format) {
// yaml
if (!isset($content)) {
$content = '';
}
$content .= sprintf(
"\n%s:\n path: %s\n defaults: { _controller: %s }\n",
$name,
$action['route'],
$controller
);
} elseif ('xml' == $format) {
// xml
if (!isset($content)) {
// new file
$content = <<<EOT
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
</routes>
EOT;
}
$sxe = simplexml_load_string($content);
$route = $sxe->addChild('route');
$route->addAttribute('id', $name);
$route->addAttribute('path', $action['route']);
$default = $route->addChild('default', $controller);
$default->addAttribute('key', '_controller');
$dom = new \DOMDocument('1.0');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($sxe->asXML());
$content = $dom->saveXML();
} elseif ('php' == $format) {
// php
if (isset($content)) {
// edit current file
$pointer = strpos($content, 'return');
if (!preg_match('/(\$[^ ]*).*?new RouteCollection\(\)/', $content, $collection) || false === $pointer) {
throw new \RunTimeException('Routing.php file is not correct, please initialize RouteCollection.');
}
$content = substr($content, 0, $pointer);
$content .= sprintf("%s->add('%s', new Route('%s', array(", $collection[1], $name, $action['route']);
$content .= sprintf("\n '_controller' => '%s',", $controller);
$content .= "\n)));\n\nreturn ".$collection[1].';';
} else {
// new file
$content = <<<EOT
<?php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
\$collection = new RouteCollection();
EOT;
$content .= sprintf("\n\$collection->add('%s', new Route('%s', array(", $name, $action['route']);
$content .= sprintf("\n '_controller' => '%s',", $controller);
$content .= "\n)));\n\nreturn \$collection;";
}
}
$flink = fopen($file, 'w');
if ($flink) {
$write = fwrite($flink, $content);
if ($write) {
fclose($flink);
} else {
throw new \RunTimeException(sprintf('We cannot write into file "%s", has that file the correct access level?', $file));
}
} else {
throw new \RunTimeException(sprintf('Problems with generating file "%s", did you gave write access to that directory?', $file));
}
}
protected function parseTemplatePath($template)
{
$data = $this->parseLogicalTemplateName($template);
return $data['controller'].'/'.$data['template'];
}
protected function parseLogicalTemplateName($logicalName, $part = '')
{
if (2 !== substr_count($logicalName, ':')) {
throw new \RuntimeException(sprintf('The given template name ("%s") is not correct (it must contain two colons).', $logicalName));
}
$data = array();
list($data['bundle'], $data['controller'], $data['template']) = explode(':', $logicalName);
return ($part ? $data[$part] : $data);
}
}

View File

@@ -0,0 +1,310 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Generator;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\Common\Inflector\Inflector;
/**
* Generates a CRUD controller.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DoctrineCrudGenerator extends Generator
{
protected $filesystem;
protected $rootDir;
protected $routePrefix;
protected $routeNamePrefix;
protected $bundle;
protected $entity;
protected $entitySingularized;
protected $entityPluralized;
protected $metadata;
protected $format;
protected $actions;
/**
* Constructor.
*
* @param Filesystem $filesystem A Filesystem instance
* @param string $rootDir The root dir
*/
public function __construct(Filesystem $filesystem, $rootDir)
{
$this->filesystem = $filesystem;
$this->rootDir = $rootDir;
}
/**
* Generate the CRUD controller.
*
* @param BundleInterface $bundle A bundle object
* @param string $entity The entity relative class name
* @param ClassMetadataInfo $metadata The entity class metadata
* @param string $format The configuration format (xml, yaml, annotation)
* @param string $routePrefix The route name prefix
* @param array $needWriteActions Whether or not to generate write actions
*
* @throws \RuntimeException
*/
public function generate(BundleInterface $bundle, $entity, ClassMetadataInfo $metadata, $format, $routePrefix, $needWriteActions, $forceOverwrite)
{
$this->routePrefix = $routePrefix;
$this->routeNamePrefix = self::getRouteNamePrefix($routePrefix);
$this->actions = $needWriteActions ? array('index', 'show', 'new', 'edit', 'delete') : array('index', 'show');
if (count($metadata->identifier) != 1) {
throw new \RuntimeException('The CRUD generator does not support entity classes with multiple or no primary keys.');
}
$this->entity = $entity;
$this->entitySingularized = lcfirst(Inflector::singularize($entity));
$this->entityPluralized = lcfirst(Inflector::pluralize($entity));
$this->bundle = $bundle;
$this->metadata = $metadata;
$this->setFormat($format);
$this->generateControllerClass($forceOverwrite);
$dir = sprintf('%s/Resources/views/%s', $this->rootDir, str_replace('\\', '/', strtolower($this->entity)));
if (!file_exists($dir)) {
$this->filesystem->mkdir($dir, 0777);
}
$this->generateIndexView($dir);
if (in_array('show', $this->actions)) {
$this->generateShowView($dir);
}
if (in_array('new', $this->actions)) {
$this->generateNewView($dir);
}
if (in_array('edit', $this->actions)) {
$this->generateEditView($dir);
}
$this->generateTestClass();
$this->generateConfiguration();
}
/**
* Sets the configuration format.
*
* @param string $format The configuration format
*/
protected function setFormat($format)
{
switch ($format) {
case 'yml':
case 'xml':
case 'php':
case 'annotation':
$this->format = $format;
break;
default:
$this->format = 'yml';
break;
}
}
/**
* Generates the routing configuration.
*/
protected function generateConfiguration()
{
if (!in_array($this->format, array('yml', 'xml', 'php'))) {
return;
}
$target = sprintf(
'%s/Resources/config/routing/%s.%s',
$this->bundle->getPath(),
strtolower(str_replace('\\', '_', $this->entity)),
$this->format
);
$this->renderFile('crud/config/routing.'.$this->format.'.twig', $target, array(
'actions' => $this->actions,
'route_prefix' => $this->routePrefix,
'route_name_prefix' => $this->routeNamePrefix,
'bundle' => $this->bundle->getName(),
'entity' => $this->entity,
));
}
/**
* Generates the controller class only.
*/
protected function generateControllerClass($forceOverwrite)
{
$dir = $this->bundle->getPath();
$parts = explode('\\', $this->entity);
$entityClass = array_pop($parts);
$entityNamespace = implode('\\', $parts);
$target = sprintf(
'%s/Controller/%s/%sController.php',
$dir,
str_replace('\\', '/', $entityNamespace),
$entityClass
);
if (!$forceOverwrite && file_exists($target)) {
throw new \RuntimeException('Unable to generate the controller as it already exists.');
}
$this->renderFile('crud/controller.php.twig', $target, array(
'actions' => $this->actions,
'route_prefix' => $this->routePrefix,
'route_name_prefix' => $this->routeNamePrefix,
'bundle' => $this->bundle->getName(),
'entity' => $this->entity,
'entity_singularized' => $this->entitySingularized,
'entity_pluralized' => $this->entityPluralized,
'entity_class' => $entityClass,
'namespace' => $this->bundle->getNamespace(),
'entity_namespace' => $entityNamespace,
'format' => $this->format,
));
}
/**
* Generates the functional test class only.
*/
protected function generateTestClass()
{
$parts = explode('\\', $this->entity);
$entityClass = array_pop($parts);
$entityNamespace = implode('\\', $parts);
$dir = $this->bundle->getPath().'/Tests/Controller';
$target = $dir.'/'.str_replace('\\', '/', $entityNamespace).'/'.$entityClass.'ControllerTest.php';
$this->renderFile('crud/tests/test.php.twig', $target, array(
'route_prefix' => $this->routePrefix,
'route_name_prefix' => $this->routeNamePrefix,
'entity' => $this->entity,
'bundle' => $this->bundle->getName(),
'entity_class' => $entityClass,
'namespace' => $this->bundle->getNamespace(),
'entity_namespace' => $entityNamespace,
'actions' => $this->actions,
'form_type_name' => strtolower(str_replace('\\', '_', $this->bundle->getNamespace()).($parts ? '_' : '').implode('_', $parts).'_'.$entityClass),
));
}
/**
* Generates the index.html.twig template in the final bundle.
*
* @param string $dir The path to the folder that hosts templates in the bundle
*/
protected function generateIndexView($dir)
{
$this->renderFile('crud/views/index.html.twig.twig', $dir.'/index.html.twig', array(
'bundle' => $this->bundle->getName(),
'entity' => $this->entity,
'entity_pluralized' => $this->entityPluralized,
'entity_singularized' => $this->entitySingularized,
'identifier' => $this->metadata->identifier[0],
'fields' => $this->metadata->fieldMappings,
'actions' => $this->actions,
'record_actions' => $this->getRecordActions(),
'route_prefix' => $this->routePrefix,
'route_name_prefix' => $this->routeNamePrefix,
));
}
/**
* Generates the show.html.twig template in the final bundle.
*
* @param string $dir The path to the folder that hosts templates in the bundle
*/
protected function generateShowView($dir)
{
$this->renderFile('crud/views/show.html.twig.twig', $dir.'/show.html.twig', array(
'bundle' => $this->bundle->getName(),
'entity' => $this->entity,
'entity_singularized' => $this->entitySingularized,
'identifier' => $this->metadata->identifier[0],
'fields' => $this->metadata->fieldMappings,
'actions' => $this->actions,
'route_prefix' => $this->routePrefix,
'route_name_prefix' => $this->routeNamePrefix,
));
}
/**
* Generates the new.html.twig template in the final bundle.
*
* @param string $dir The path to the folder that hosts templates in the bundle
*/
protected function generateNewView($dir)
{
$this->renderFile('crud/views/new.html.twig.twig', $dir.'/new.html.twig', array(
'bundle' => $this->bundle->getName(),
'entity' => $this->entity,
'entity_singularized' => $this->entitySingularized,
'route_prefix' => $this->routePrefix,
'route_name_prefix' => $this->routeNamePrefix,
'actions' => $this->actions,
'fields' => $this->metadata->fieldMappings,
));
}
/**
* Generates the edit.html.twig template in the final bundle.
*
* @param string $dir The path to the folder that hosts templates in the bundle
*/
protected function generateEditView($dir)
{
$this->renderFile('crud/views/edit.html.twig.twig', $dir.'/edit.html.twig', array(
'route_prefix' => $this->routePrefix,
'route_name_prefix' => $this->routeNamePrefix,
'identifier' => $this->metadata->identifier[0],
'entity' => $this->entity,
'entity_singularized' => $this->entitySingularized,
'fields' => $this->metadata->fieldMappings,
'bundle' => $this->bundle->getName(),
'actions' => $this->actions,
));
}
/**
* Returns an array of record actions to generate (edit, show).
*
* @return array
*/
protected function getRecordActions()
{
return array_filter($this->actions, function ($item) {
return in_array($item, array('show', 'edit'));
});
}
public static function getRouteNamePrefix($prefix)
{
$prefix = preg_replace('/{(.*?)}/', '', $prefix); // {foo}_bar -> _bar
$prefix = str_replace('/', '_', $prefix);
$prefix = preg_replace('/_+/', '_', $prefix); // foo__bar -> foo_bar
$prefix = trim($prefix, '_');
return $prefix;
}
}

View File

@@ -0,0 +1,150 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Generator;
use Sensio\Bundle\GeneratorBundle\Model\EntityGeneratorResult;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Symfony\Bridge\Doctrine\RegistryInterface;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Tools\EntityGenerator;
use Doctrine\ORM\Tools\EntityRepositoryGenerator;
use Doctrine\ORM\Tools\Export\ClassMetadataExporter;
use Doctrine\Common\Util\Inflector;
/**
* Generates a Doctrine entity class based on its name, fields and format.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jonathan H. Wage <jonwage@gmail.com>
*/
class DoctrineEntityGenerator extends Generator
{
private $filesystem;
private $registry;
public function __construct(Filesystem $filesystem, RegistryInterface $registry)
{
$this->filesystem = $filesystem;
$this->registry = $registry;
}
/**
* @param BundleInterface $bundle
* @param string $entity
* @param string $format
* @param array $fields
*
* @return EntityGeneratorResult
*
* @throws \Doctrine\ORM\Tools\Export\ExportException
*/
public function generate(BundleInterface $bundle, $entity, $format, array $fields)
{
// configure the bundle (needed if the bundle does not contain any Entities yet)
$config = $this->registry->getManager(null)->getConfiguration();
$config->setEntityNamespaces(array_merge(
array($bundle->getName() => $bundle->getNamespace().'\\Entity'),
$config->getEntityNamespaces()
));
$entityClass = $this->registry->getAliasNamespace($bundle->getName()).'\\'.$entity;
$entityPath = $bundle->getPath().'/Entity/'.str_replace('\\', '/', $entity).'.php';
if (file_exists($entityPath)) {
throw new \RuntimeException(sprintf('Entity "%s" already exists.', $entityClass));
}
$class = new ClassMetadataInfo($entityClass, $config->getNamingStrategy());
$class->customRepositoryClassName = str_replace('\\Entity\\', '\\Repository\\', $entityClass).'Repository';
$class->mapField(array('fieldName' => 'id', 'type' => 'integer', 'id' => true));
$class->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO);
foreach ($fields as $field) {
$class->mapField($field);
}
$entityGenerator = $this->getEntityGenerator();
if ('annotation' === $format) {
$entityGenerator->setGenerateAnnotations(true);
$class->setPrimaryTable(array('name' => Inflector::tableize($entity)));
$entityCode = $entityGenerator->generateEntityClass($class);
$mappingPath = $mappingCode = false;
} else {
$cme = new ClassMetadataExporter();
$exporter = $cme->getExporter('yml' == $format ? 'yaml' : $format);
$mappingPath = $bundle->getPath().'/Resources/config/doctrine/'.str_replace('\\', '.', $entity).'.orm.'.$format;
if (file_exists($mappingPath)) {
throw new \RuntimeException(sprintf('Cannot generate entity when mapping "%s" already exists.', $mappingPath));
}
$mappingCode = $exporter->exportClassMetadata($class);
$entityGenerator->setGenerateAnnotations(false);
$entityCode = $entityGenerator->generateEntityClass($class);
}
$entityCode = str_replace(
array("@var integer\n", "@var boolean\n", "@param integer\n", "@param boolean\n", "@return integer\n", "@return boolean\n"),
array("@var int\n", "@var bool\n", "@param int\n", "@param bool\n", "@return int\n", "@return bool\n"),
$entityCode
);
$this->filesystem->mkdir(dirname($entityPath));
file_put_contents($entityPath, $entityCode);
if ($mappingPath) {
$this->filesystem->mkdir(dirname($mappingPath));
file_put_contents($mappingPath, $mappingCode);
}
$path = $bundle->getPath().str_repeat('/..', substr_count(get_class($bundle), '\\'));
$this->getRepositoryGenerator()->writeEntityRepositoryClass($class->customRepositoryClassName, $path);
$repositoryPath = $path.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $class->customRepositoryClassName).'.php';
return new EntityGeneratorResult($entityPath, $repositoryPath, $mappingPath);
}
public function isReservedKeyword($keyword)
{
return $this->registry->getConnection()->getDatabasePlatform()->getReservedKeywordsList()->isKeyword($keyword);
}
protected function getEntityGenerator()
{
$entityGenerator = new EntityGenerator();
$entityGenerator->setGenerateAnnotations(false);
$entityGenerator->setGenerateStubMethods(true);
$entityGenerator->setRegenerateEntityIfExists(false);
$entityGenerator->setUpdateEntityIfExists(true);
$entityGenerator->setNumSpaces(4);
$entityGenerator->setAnnotationPrefix('ORM\\');
return $entityGenerator;
}
protected function getRepositoryGenerator()
{
return new EntityRepositoryGenerator();
}
/**
* Checks if the given name is a valid PHP variable name.
*
* @see http://php.net/manual/en/language.variables.basics.php
*
* @param $name string
*
* @return bool
*/
public function isValidPhpVariableName($name)
{
return (bool) preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $name, $matches);
}
}

View File

@@ -0,0 +1,120 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Generator;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
/**
* Generates a form class based on a Doctrine entity.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Hugo Hamon <hugo.hamon@sensio.com>
*/
class DoctrineFormGenerator extends Generator
{
private $filesystem;
private $className;
private $classPath;
/**
* Constructor.
*
* @param Filesystem $filesystem A Filesystem instance
*/
public function __construct(Filesystem $filesystem)
{
$this->filesystem = $filesystem;
}
public function getClassName()
{
return $this->className;
}
public function getClassPath()
{
return $this->classPath;
}
/**
* Generates the entity form class.
*
* @param BundleInterface $bundle The bundle in which to create the class
* @param string $entity The entity relative class name
* @param ClassMetadataInfo $metadata The entity metadata class
* @param bool $forceOverwrite If true, remove any existing form class before generating it again
*/
public function generate(BundleInterface $bundle, $entity, ClassMetadataInfo $metadata, $forceOverwrite = false)
{
$parts = explode('\\', $entity);
$entityClass = array_pop($parts);
$this->className = $entityClass.'Type';
$dirPath = $bundle->getPath().'/Form';
$this->classPath = $dirPath.'/'.str_replace('\\', '/', $entity).'Type.php';
if (!$forceOverwrite && file_exists($this->classPath)) {
throw new \RuntimeException(sprintf('Unable to generate the %s form class as it already exists under the %s file', $this->className, $this->classPath));
}
if (count($metadata->identifier) > 1) {
throw new \RuntimeException('The form generator does not support entity classes with multiple primary keys.');
}
$parts = explode('\\', $entity);
array_pop($parts);
$this->renderFile('form/FormType.php.twig', $this->classPath, array(
'fields' => $this->getFieldsFromMetadata($metadata),
'fields_mapping' => $metadata->fieldMappings,
'namespace' => $bundle->getNamespace(),
'entity_namespace' => implode('\\', $parts),
'entity_class' => $entityClass,
'bundle' => $bundle->getName(),
'form_class' => $this->className,
'form_type_name' => strtolower(str_replace('\\', '_', $bundle->getNamespace()).($parts ? '_' : '').implode('_', $parts).'_'.substr($this->className, 0, -4)),
// Add 'setDefaultOptions' method with deprecated type hint, if the new 'configureOptions' isn't available.
// Required as long as Symfony 2.6 is supported.
'configure_options_available' => method_exists('Symfony\Component\Form\AbstractType', 'configureOptions'),
'get_name_required' => !method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix'),
));
}
/**
* Returns an array of fields. Fields can be both column fields and
* association fields.
*
* @param ClassMetadataInfo $metadata
*
* @return array $fields
*/
private function getFieldsFromMetadata(ClassMetadataInfo $metadata)
{
$fields = (array) $metadata->fieldNames;
// Remove the primary key field if it's not managed manually
if (!$metadata->isIdentifierNatural()) {
$fields = array_diff($fields, $metadata->identifier);
}
foreach ($metadata->associationMappings as $fieldName => $relation) {
if ($relation['type'] !== ClassMetadataInfo::ONE_TO_MANY) {
$fields[] = $fieldName;
}
}
return $fields;
}
}

View File

@@ -0,0 +1,66 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Generator;
/**
* Generator is the base class for all generators.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Generator
{
private $skeletonDirs;
/**
* Sets an array of directories to look for templates.
*
* The directories must be sorted from the most specific to the most
* directory.
*
* @param array $skeletonDirs An array of skeleton dirs
*/
public function setSkeletonDirs($skeletonDirs)
{
$this->skeletonDirs = is_array($skeletonDirs) ? $skeletonDirs : array($skeletonDirs);
}
protected function render($template, $parameters)
{
$twig = $this->getTwigEnvironment();
return $twig->render($template, $parameters);
}
/**
* Get the twig environment that will render skeletons.
*
* @return \Twig_Environment
*/
protected function getTwigEnvironment()
{
return new \Twig_Environment(new \Twig_Loader_Filesystem($this->skeletonDirs), array(
'debug' => true,
'cache' => false,
'strict_variables' => true,
'autoescape' => false,
));
}
protected function renderFile($template, $target, $parameters)
{
if (!is_dir(dirname($target))) {
mkdir(dirname($target), 0777, true);
}
return file_put_contents($target, $this->render($template, $parameters));
}
}

View File

@@ -0,0 +1,19 @@
Copyright (c) 2011-2013 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,116 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Manipulator;
use Sensio\Bundle\GeneratorBundle\Model\Bundle;
use Symfony\Component\Yaml\Yaml;
/**
* Changes the PHP code of a YAML services configuration file.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Ryan Weaver <weaverryan@gmail.com>
*/
class ConfigurationManipulator extends Manipulator
{
private $file;
/**
* Constructor.
*
* @param string $file The YAML configuration file path
*/
public function __construct($file)
{
$this->file = $file;
}
/**
* Adds a configuration resource at the top of the existing ones.
*
* @param Bundle $bundle
*
* @throws \RuntimeException If this process fails for any reason
*/
public function addResource(Bundle $bundle)
{
// if the config.yml file doesn't exist, don't even try.
if (!file_exists($this->file)) {
throw new \RuntimeException(sprintf('The target config file %s does not exist', $this->file));
}
$code = $this->getImportCode($bundle);
$currentContents = file_get_contents($this->file);
// Don't add same bundle twice
if (false !== strpos($currentContents, $code)) {
throw new \RuntimeException(sprintf(
'The %s configuration file from %s is already imported',
$bundle->getServicesConfigurationFilename(),
$bundle->getName()
));
}
// find the "imports" line and add this at the end of that list
$lastImportedPath = $this->findLastImportedPath($currentContents);
if (!$lastImportedPath) {
throw new \RuntimeException(sprintf('Could not find the imports key in %s', $this->file));
}
// find imports:
$importsPosition = strpos($currentContents, 'imports:');
// find the last import
$lastImportPosition = strpos($currentContents, $lastImportedPath, $importsPosition);
// find the line break after the last import
$targetLinebreakPosition = strpos($currentContents, "\n", $lastImportPosition);
$newContents = substr($currentContents, 0, $targetLinebreakPosition)."\n".$code.substr($currentContents, $targetLinebreakPosition);
if (false === file_put_contents($this->file, $newContents)) {
throw new \RuntimeException(sprintf('Could not write file %s ', $this->file));
}
}
public function getImportCode(Bundle $bundle)
{
return sprintf(<<<EOF
- { resource: "@%s/Resources/config/%s" }
EOF
,
$bundle->getName(),
$bundle->getServicesConfigurationFilename()
);
}
/**
* Finds the last imported resource path in the YAML file.
*
* @param $yamlContents
*
* @return bool|string
*/
private function findLastImportedPath($yamlContents)
{
$data = Yaml::parse($yamlContents);
if (!isset($data['imports'])) {
return false;
}
// find the last imports entry
$lastImport = end($data['imports']);
if (!isset($lastImport['resource'])) {
return false;
}
return $lastImport['resource'];
}
}

View File

@@ -0,0 +1,129 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Manipulator;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* Changes the PHP code of a Kernel.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class KernelManipulator extends Manipulator
{
protected $kernel;
protected $reflected;
/**
* Constructor.
*
* @param KernelInterface $kernel A KernelInterface instance
*/
public function __construct(KernelInterface $kernel)
{
$this->kernel = $kernel;
$this->reflected = new \ReflectionObject($kernel);
}
/**
* Adds a bundle at the end of the existing ones.
*
* @param string $bundle The bundle class name
*
* @return bool true if it worked, false otherwise
*
* @throws \RuntimeException If bundle is already defined
*/
public function addBundle($bundle)
{
if (!$this->getFilename()) {
return false;
}
$src = file($this->getFilename());
$method = $this->reflected->getMethod('registerBundles');
$lines = array_slice($src, $method->getStartLine() - 1, $method->getEndLine() - $method->getStartLine() + 1);
// Don't add same bundle twice
if (false !== strpos(implode('', $lines), $bundle)) {
throw new \RuntimeException(sprintf('Bundle "%s" is already defined in "AppKernel::registerBundles()".', $bundle));
}
$this->setCode(token_get_all('<?php '.implode('', $lines)), $method->getStartLine());
while ($token = $this->next()) {
// $bundles
if (T_VARIABLE !== $token[0] || '$bundles' !== $token[1]) {
continue;
}
// =
$this->next();
// array start with traditional or short syntax
$token = $this->next();
if (T_ARRAY !== $token[0] && '[' !== $this->value($token)) {
return false;
}
// add the bundle at the end of the array
while ($token = $this->next()) {
// look for ); or ];
if (')' !== $this->value($token) && ']' !== $this->value($token)) {
continue;
}
if (';' !== $this->value($this->peek())) {
continue;
}
$this->next();
$leadingContent = implode('', array_slice($src, 0, $this->line));
// trim semicolon
$leadingContent = rtrim(rtrim($leadingContent), ';');
// We want to match ) & ]
$closingSymbolRegex = '#(\)|])$#';
// get closing symbol used
preg_match($closingSymbolRegex, $leadingContent, $matches);
$closingSymbol = $matches[0];
// remove last close parentheses
$leadingContent = rtrim(preg_replace($closingSymbolRegex, '', rtrim($leadingContent)));
if (substr($leadingContent, -1) !== '(' && substr($leadingContent, -1) !== '[' ) {
// end of leading content is not open parentheses or bracket, then assume that array contains at least one element
$leadingContent = rtrim($leadingContent, ',').',';
}
$lines = array_merge(
array($leadingContent, "\n"),
array(str_repeat(' ', 12), sprintf('new %s(),', $bundle), "\n"),
array(str_repeat(' ', 8), $closingSymbol.';', "\n"),
array_slice($src, $this->line)
);
file_put_contents($this->getFilename(), implode('', $lines));
return true;
}
}
}
public function getFilename()
{
return $this->reflected->getFileName();
}
}

View File

@@ -0,0 +1,88 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Manipulator;
/**
* Changes the PHP code of a Kernel.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Manipulator
{
protected $tokens;
protected $line;
/**
* Sets the code to manipulate.
*
* @param array $tokens An array of PHP tokens
* @param int $line The start line of the code
*/
protected function setCode(array $tokens, $line = 0)
{
$this->tokens = $tokens;
$this->line = $line;
}
/**
* Gets the next token.
*
* @return mixed
*/
protected function next()
{
while ($token = array_shift($this->tokens)) {
$this->line += substr_count($this->value($token), "\n");
if (is_array($token) && in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT))) {
continue;
}
return $token;
}
}
/**
* Peeks the next token.
*
* @param int $nb
*
* @return mixed
*/
protected function peek($nb = 1)
{
$i = 0;
$tokens = $this->tokens;
while ($token = array_shift($tokens)) {
if (is_array($token) && in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT))) {
continue;
}
++$i;
if ($i == $nb) {
return $token;
}
}
}
/**
* Gets the value of a token.
*
* @param string|string[] $token The token value
*
* @return string
*/
protected function value($token)
{
return is_array($token) ? $token[1] : $token;
}
}

View File

@@ -0,0 +1,142 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\GeneratorBundle\Manipulator;
use Symfony\Component\DependencyInjection\Container;
use Sensio\Bundle\GeneratorBundle\Generator\DoctrineCrudGenerator;
use Symfony\Component\Yaml\Yaml;
/**
* Changes the PHP code of a YAML routing file.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class RoutingManipulator extends Manipulator
{
private $file;
/**
* Constructor.
*
* @param string $file The YAML routing file path
*/
public function __construct($file)
{
$this->file = $file;
}
/**
* Adds a routing resource at the top of the existing ones.
*
* @param string $bundle
* @param string $format
* @param string $prefix
* @param string $path
*
* @return bool true if it worked, false otherwise
*
* @throws \RuntimeException If bundle is already imported
*/
public function addResource($bundle, $format, $prefix = '/', $path = 'routing')
{
$current = '';
$code = sprintf("%s:\n", $this->getImportedResourceYamlKey($bundle, $prefix));
if (file_exists($this->file)) {
$current = file_get_contents($this->file);
// Don't add same bundle twice
if (false !== strpos($current, '@'.$bundle)) {
throw new \RuntimeException(sprintf('Bundle "%s" is already imported.', $bundle));
}
} elseif (!is_dir($dir = dirname($this->file))) {
mkdir($dir, 0777, true);
}
if ('annotation' == $format) {
$code .= sprintf(" resource: \"@%s/Controller/\"\n type: annotation\n", $bundle);
} else {
$code .= sprintf(" resource: \"@%s/Resources/config/%s.%s\"\n", $bundle, $path, $format);
}
$code .= sprintf(" prefix: %s\n", $prefix);
$code .= "\n";
$code .= $current;
if (false === file_put_contents($this->file, $code)) {
return false;
}
return true;
}
/**
* Check if the routing file contain a line for the bundle.
*
* @param string $bundle
*
* @return bool
*/
public function hasResourceInAnnotation($bundle)
{
if (!file_exists($this->file)) {
return false;
}
$config = Yaml::parse(file_get_contents($this->file));
$search = sprintf('@%s/Controller/', $bundle);
foreach ($config as $resource) {
if (array_key_exists('resource', $resource)) {
return $resource['resource'] === $search;
}
}
return false;
}
/**
* Add an annotation controller resource.
*
* @param string $bundle
* @param string $controller
*
* @return bool
*/
public function addAnnotationController($bundle, $controller)
{
$current = '';
if (file_exists($this->file)) {
$current = file_get_contents($this->file);
} elseif (!is_dir($dir = dirname($this->file))) {
mkdir($dir, 0777, true);
}
$code = sprintf("%s:\n", Container::underscore(substr($bundle, 0, -6)).'_'.Container::underscore($controller));
$code .= sprintf(" resource: \"@%s/Controller/%sController.php\"\n type: annotation\n", $bundle, $controller);
$code .= "\n";
$code .= $current;
return false !== file_put_contents($this->file, $code);
}
public function getImportedResourceYamlKey($bundle, $prefix)
{
$snakeCasedBundleName = Container::underscore(substr($bundle, 0, -6));
$routePrefix = DoctrineCrudGenerator::getRouteNamePrefix($prefix);
return sprintf('%s%s%s', $snakeCasedBundleName, '' !== $routePrefix ? '_' : '' , $routePrefix);
}
}

View File

@@ -0,0 +1,143 @@
<?php
namespace Sensio\Bundle\GeneratorBundle\Model;
use Symfony\Component\DependencyInjection\Container;
/**
* Represents a bundle being built.
*/
class Bundle
{
private $namespace;
private $name;
private $targetDirectory;
private $configurationFormat;
private $isShared;
private $testsDirectory;
public function __construct($namespace, $name, $targetDirectory, $configurationFormat, $isShared)
{
$this->namespace = $namespace;
$this->name = $name;
$this->targetDirectory = $targetDirectory;
$this->configurationFormat = $configurationFormat;
$this->isShared = $isShared;
$this->testsDirectory = $this->getTargetDirectory().'/Tests';
}
public function getNamespace()
{
return $this->namespace;
}
public function getName()
{
return $this->name;
}
public function getConfigurationFormat()
{
return $this->configurationFormat;
}
public function isShared()
{
return $this->isShared;
}
/**
* Returns the directory where the bundle will be generated.
*
* @return string
*/
public function getTargetDirectory()
{
return rtrim($this->targetDirectory, '/').'/'.trim(strtr($this->namespace, '\\', '/'), '/');
}
/**
* Returns the name of the bundle without the Bundle suffix.
*
* @return string
*/
public function getBasename()
{
return substr($this->name, 0, -6);
}
/**
* Returns the dependency injection extension alias for this bundle.
*
* @return string
*/
public function getExtensionAlias()
{
return Container::underscore($this->getBasename());
}
/**
* Should a DependencyInjection directory be generated for this bundle?
*
* @return bool
*/
public function shouldGenerateDependencyInjectionDirectory()
{
return $this->isShared;
}
/**
* What is the filename for the services.yml/xml file?
*
* @return string
*/
public function getServicesConfigurationFilename()
{
if ('yml' === $this->getConfigurationFormat() || 'annotation' === $this->configurationFormat) {
return 'services.yml';
} else {
return 'services.'.$this->getConfigurationFormat();
}
}
/**
* What is the filename for the routing.yml/xml file?
*
* If false, no routing file will be generated
*
* @return string|bool
*/
public function getRoutingConfigurationFilename()
{
if ($this->getConfigurationFormat() == 'annotation') {
return false;
}
return 'routing.'.$this->getConfigurationFormat();
}
/**
* Returns the class name of the Bundle class.
*
* @return string
*/
public function getBundleClassName()
{
return $this->namespace.'\\'.$this->name;
}
public function setTestsDirectory($testsDirectory)
{
$this->testsDirectory = $testsDirectory;
}
public function getTestsDirectory()
{
return $this->testsDirectory;
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Sensio\Bundle\GeneratorBundle\Model;
class EntityGeneratorResult
{
/** @var string */
private $entityPath;
/** @var string */
private $repositoryPath;
/** @var string */
private $mappingPath;
/**
* @param string $entityPath
* @param string $repositoryPath
* @param string $mappingPath
*/
public function __construct($entityPath, $repositoryPath, $mappingPath)
{
$this->entityPath = $entityPath;
$this->repositoryPath = $repositoryPath;
$this->mappingPath = $mappingPath;
}
/**
* @return string
*/
public function getEntityPath()
{
return $this->entityPath;
}
/**
* @return string
*/
public function getRepositoryPath()
{
return $this->repositoryPath;
}
/**
* @return string
*/
public function getMappingPath()
{
return $this->mappingPath;
}
}

View File

@@ -0,0 +1,10 @@
SensioGeneratorBundle
=====================
The `SensioGeneratorBundle` extends the default Symfony2 command line
interface by providing new interactive and intuitive commands for generating
code skeletons like bundles, form classes, or CRUD controllers based on a
Doctrine 2 schema.
More information in the official
[documentation](http://symfony.com/doc/current/bundles/SensioGeneratorBundle/index.html).

View File

@@ -0,0 +1,15 @@
<?php
namespace {{ namespace }};
{% block use_statements %}
use Symfony\Component\HttpKernel\Bundle\Bundle;
{% endblock use_statements %}
{% block class_definition %}
class {{ bundle }} extends Bundle
{% endblock class_definition %}
{
{% block class_body %}
{% endblock class_body %}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace {{ namespace }}\DependencyInjection;
{% block use_statements %}
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
{% endblock use_statements %}
/**
{% block phpdoc_class_header %}
* This is the class that validates and merges configuration from your app/config files.
{% endblock phpdoc_class_header %}
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/configuration.html}
*/
{% block class_definition %}
class Configuration implements ConfigurationInterface
{% endblock class_definition %}
{
{% block class_body %}
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('{{ extension_alias }}');
// Here you should define the parameters that are allowed to
// configure your bundle. See the documentation linked above for
// more information on that topic.
return $treeBuilder;
}
{% endblock class_body %}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace {{ namespace }}\Controller;
{% block use_statements %}
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
{% if 'annotation' == format -%}
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
{% endif %}
{% endblock use_statements %}
{% block class_definition %}
class DefaultController extends Controller
{% endblock class_definition %}
{
{% block class_body %}
{% if 'annotation' == format -%}
/**
* @Route("/")
*/
{% endif -%}
public function indexAction()
{
return $this->render('{{ bundle }}:Default:index.html.twig');
}
{% endblock class_body %}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace {{ namespace }}\Tests\Controller;
{% block use_statements %}
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
{% endblock use_statements %}
{% block class_definition %}
class DefaultControllerTest extends WebTestCase
{% endblock class_definition %}
{
{% block class_body %}
public function testIndex()
{
$client = static::createClient();
$crawler = $client->request('GET', '/');
$this->assertContains('Hello World', $client->getResponse()->getContent());
}
{% endblock class_body %}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace {{ namespace }}\DependencyInjection;
{% block use_statements %}
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
{% endblock use_statements %}
/**
{% block phpdoc_class_header %}
* This is the class that loads and manages your bundle configuration.
{% endblock phpdoc_class_header %}
*
* @link http://symfony.com/doc/current/cookbook/bundles/extension.html
*/
{% block class_definition %}
class {{ bundle_basename }}Extension extends Extension
{% endblock class_definition %}
{
{% block class_body %}
/**
* {@inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
{% if format == 'yml' or format == 'annotation' -%}
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
{%- elseif format == 'xml' -%}
$loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.xml');
{%- elseif format == 'php' -%}
$loader = new Loader\PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.php');
{%- endif %}
}
{% endblock class_body %}
}

View File

@@ -0,0 +1,20 @@
<?php
{% block use_statements %}
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
{% endblock use_statements %}
{% block definition %}
$collection = new RouteCollection();
{% endblock definition %}
{% block body %}
$collection->add('{{ extension_alias }}_homepage', new Route('/', array(
'_controller' => '{{ bundle }}:Default:index',
)));
{% endblock body %}
{% block return %}
return $collection;
{% endblock return %}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
{% block body %}
<route id="{{ extension_alias }}_homepage" path="/">
<default key="_controller">{{ bundle }}:Default:index</default>
</route>
{% endblock body %}
</routes>

View File

@@ -0,0 +1,3 @@
{{ extension_alias }}_homepage:
path: /
defaults: { _controller: {{ bundle }}:Default:index }

View File

@@ -0,0 +1,25 @@
<?php
{% block use_statements %}
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Parameter;
{% endblock use_statements %}
/*
{% block services %}
$container->setDefinition(
'{{ extension_alias }}.example',
new Definition(
'{{ namespace }}\Example',
array(
new Reference('service_id'),
"plain_value",
new Parameter('parameter_name'),
)
)
);
{% endblock services %}
*/

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<!--
<services>
{% block services %}
<service id="{{ extension_alias }}.example" class="{{ namespace }}\Example">
<argument type="service" id="service_id" />
<argument>plain_value</argument>
<argument>%parameter_name%</argument>
</service>
{% endblock services %}
</services>
-->
</container>

View File

@@ -0,0 +1,6 @@
services:
{% block services %}
# {{ extension_alias }}.example:
# class: {{ namespace }}\Example
# arguments: ["@service_id", "plain_value", %parameter%]
{% endblock services %}

View File

@@ -0,0 +1,40 @@
<?php
namespace {{ namespace }}\Command;
{% block use_statements %}
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
{% endblock use_statements %}
{% block class_definition %}
class {{ class_name }} extends ContainerAwareCommand
{% endblock class_definition %}
{
{% block class_body %}
protected function configure()
{
$this
->setName('{{ name }}')
->setDescription('...')
->addArgument('argument', InputArgument::OPTIONAL, 'Argument description')
->addOption('option', null, InputOption::VALUE_NONE, 'Option description')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$argument = $input->getArgument('argument');
if ($input->getOption('option')) {
// ...
}
$output->writeln('Command result.');
}
{% endblock class_body %}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace {{ namespace }}\Controller;
{% block use_statements %}
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
{% if 'annotation' == format.routing -%}
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
{% endif %}
{% endblock use_statements %}
{% block class_definition %}
class {{ controller }}Controller extends Controller
{% endblock class_definition %}
{
{% block class_body %}
{% for action in actions %}
{% if 'annotation' == format.routing -%}
/**
* @Route("{{ action.route }}")
*/
{% endif -%}
public function {{ action.name }}(
{%- if action.placeholders|length > 0 -%}
${{- action.placeholders|join(', $') -}}
{%- endif -%})
{
{% if 'default' == action.template -%}
return $this->render('{{ bundle }}:{{ controller }}:{{ action.name|slice(0, -6) }}.html.{{ format.templating }}', array(
// ...
));
{%- else -%}
return $this->render('{{ action.template }}', array(
// ...
));
{%- endif %}
}
{% endfor -%}
{% endblock class_body %}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace {{ namespace }}\Tests\Controller;
{% block use_statements %}
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
{% endblock use_statements %}
{% block class_definition %}
class {{ controller }}ControllerTest extends WebTestCase
{% endblock class_definition %}
{
{% block class_body %}
{% for action in actions %}
public function test{{ action.basename|capitalize }}()
{
$client = static::createClient();
$crawler = $client->request('GET', '{{ action.route }}');
}
{% endfor -%}
{% endblock class_body %}
}

View File

@@ -0,0 +1,7 @@
<?php $view->extend('::base.html.twig') ?>
<?php $view['slots']->set('title', '{{ bundle }}:{{ controller }}:{{ action.basename }}') ?>
<?php $view['slots']->start('body') ?>
<h1>Welcome to the {{ controller }}:{{ action.basename }} page</h1>
<?php $view['slots']->stop() ?>

View File

@@ -0,0 +1,9 @@
{{ '{% extends "::base.html.twig" %}' }}
{{ '{% block title %}' -}}
{{ bundle }}:{{ controller }}:{{ action.basename }}
{{- '{% endblock %}' }}
{{ '{% block body %}' }}
<h1>Welcome to the {{ controller }}:{{ action.basename }} page</h1>
{{ '{% endblock %}' }}

View File

@@ -0,0 +1,50 @@
/**
{% block phpdoc_method_header %}
* Deletes a {{ entity }} entity.
{% endblock phpdoc_method_header %}
*
{% block phpdoc_method_annotations %}
{% if 'annotation' == format %}
* @Route("/{id}", name="{{ route_name_prefix }}_delete")
* @Method("DELETE")
{% endif %}
{% endblock phpdoc_method_annotations %}
*/
{% block method_definition %}
public function deleteAction(Request $request, {{ entity_class }} ${{ entity_singularized }})
{% endblock method_definition %}
{
{% block method_body %}
$form = $this->createDeleteForm(${{ entity_singularized }});
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->remove(${{ entity_singularized }});
$em->flush();
}
{% endblock method_body %}
{% block method_return %}
return $this->redirectToRoute('{{ route_name_prefix }}_index');
{% endblock method_return %}
}
{% block form %}
/**
* Creates a form to delete a {{ entity }} entity.
*
* @param {{ entity_class }} ${{ entity_singularized }} The {{ entity }} entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createDeleteForm({{ entity_class }} ${{ entity_singularized }})
{
return $this->createFormBuilder()
->setAction($this->generateUrl('{{ route_name_prefix }}_delete', array('id' => ${{ entity_singularized }}->getId())))
->setMethod('DELETE')
->getForm()
;
}
{% endblock form %}

Some files were not shown because too many files have changed in this diff Show More