This commit is contained in:
Kevin Adametz 2020-03-07 19:46:02 +01:00
parent a37785b391
commit 33458b2ca3
9915 changed files with 1247019 additions and 0 deletions

View file

@ -0,0 +1,3 @@
phpunit.xml
vendor
composer.lock

View file

@ -0,0 +1,28 @@
language: php
dist: trusty
sudo: false
cache:
directories:
- $HOME/.composer/cache/files
- .phpunit
matrix:
include:
- php: hhvm
- php: 5.3
dist: precise
- php: 5.4
- php: 5.5
- php: 5.6
- php: 7.0
- php: 7.1
env: composer_extra="--prefer-lowest --prefer-stable"
fast_finish: true
install:
- composer $composer_extra update
script:
- ./vendor/bin/simple-phpunit

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>',
$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 problems.',
'',
));
$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->writeln('> 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->writeln(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->writeln(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->writeln(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(
sprintf('- Import the bundle\'s "%s" resource in the app\'s main configuration file:', $bundle->getServicesConfigurationFilename()),
'',
$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,167 @@
<?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>%command.name%</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 %command.full_name% 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>)',
'',
));
$bundleNames = array_keys($this->getContainer()->get('kernel')->getBundles());
$question = new Question($questionHelper->getQuestion('Bundle name', $bundle), $bundle);
$question->setAutocompleterValues($bundleNames);
$question->setValidator(function ($answer) use ($bundleNames) {
if (!in_array($answer, $bundleNames)) {
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
$questionHelper->writeSection($output, 'Summary before generation');
$output->writeln(array(
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,358 @@
<?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\RoutingManipulator;
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');
$routingFormat = $input->getOption('route-format');
/** @var ControllerGenerator $generator */
$generator = $this->getGenerator($bundle);
$generator->generate(
$bundle,
$controller,
$routingFormat,
$input->getOption('template-format'),
$this->parseActions($input->getOption('actions'))
);
if ('annotations' === $routingFormat) {
$this->tryUpdateAnnotationRouting($bundle, $controller);
}
$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 Symfony 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>',
'',
));
$bundleNames = array_keys($this->getContainer()->get('kernel')->getBundles());
while (true) {
$question = new Question($questionHelper->getQuestion('Controller name', $input->getOption('controller')), $input->getOption('controller'));
$question->setAutocompleterValues($bundleNames);
$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
$questionHelper->writeSection($output, 'Summary before generation');
$output->writeln(array(
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'));
}
private function tryUpdateAnnotationRouting($bundleName, $controller)
{
$routing = new RoutingManipulator($this->getContainer()->getParameter('kernel.root_dir').'/config/routing.yml');
if ($routing->hasResourceInAnnotation($bundleName)) {
return;
}
$routing->addAnnotationController($bundleName, $controller);
}
}

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,351 @@
<?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')
->addArgument('entity', InputArgument::OPTIONAL, 'The entity class name to initialize (shortcut notation)')
->addOption('entity', null, InputOption::VALUE_OPTIONAL, 'The entity class name to initialize (shortcut notation)')
->addOption('route-prefix', null, InputOption::VALUE_REQUIRED, 'The route prefix')
->addOption('with-write', null, InputOption::VALUE_NONE, 'Whether or not to generate create, new and delete actions')
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The format used for configuration files (php, xml, yml, or annotation)', 'annotation')
->addOption('overwrite', null, 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% 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% 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;
}
} else {
// BC to be removed in 4.0
if ($input->hasOption('entity') && $entityOption = $input->getOption('entity')) {
@trigger_error('Using the "--entity" option has been deprecated since version 3.0 and will be removed in 4.0. Pass it as argument instead.', E_USER_DEPRECATED);
$input->setArgument('entity', $entityOption);
}
}
$entity = Validators::validateEntityName($input->getArgument('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->hasOption('entity') && $entityOption = $input->getOption('entity')) {
@trigger_error('Using the "--entity" option has been deprecated since version 3.0 and will be removed in 4.0. Pass it as argument instead.', E_USER_DEPRECATED);
$input->setArgument('entity', $entityOption);
}
$question = new Question($questionHelper->getQuestion('The Entity shortcut name', $input->getArgument('entity')), $input->getArgument('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->setArgument('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
$questionHelper->writeSection($output, 'Summary before generation');
$output->writeln(array(
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,414 @@
<?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\InputArgument;
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')
->addArgument('entity', InputArgument::OPTIONAL, 'The entity class name to initialize (shortcut notation)')
->addOption('entity', null, InputOption::VALUE_OPTIONAL, '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% 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% 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% AcmeBlogBundle:Blog/Post --format=yml</info>
To deactivate the interaction mode, simply use the <comment>--no-interaction</comment> option or its
alias <comment>-n</comment>, without forgetting to pass all needed options:
<info>php %command.full_name% AcmeBlogBundle:Blog/Post -n --format=annotation --fields="title:string(255) body:text"</info>
This also has support for passing field specific attributes:
<info>php %command.full_name% AcmeBlogBundle:Blog/Post -n --format=annotation --fields="title:string(length=255 nullable=true unique=true) body:text ranking:decimal(precision=10 scale=0)"</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();
// BC to be removed in 4.0
if (!$input->isInteractive() && $input->hasOption('entity') && $entityOption = $input->getOption('entity')) {
@trigger_error('Using the "--entity" option has been deprecated since version 3.0 and will be removed in 4.0. Pass it as argument instead.', E_USER_DEPRECATED);
$input->setArgument('entity', $entityOption);
}
$entity = Validators::validateEntityName($input->getArgument('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>.',
'',
));
if ($input->hasOption('entity') && $entityOption = $input->getOption('entity')) {
@trigger_error('Using the "--entity" option has been deprecated since version 3.0 and will be removed in 4.0. Pass it as argument instead.', E_USER_DEPRECATED);
$input->setArgument('entity', $entityOption);
}
$bundleNames = array_keys($this->getContainer()->get('kernel')->getBundles());
while (true) {
$question = new Question($questionHelper->getQuestion('The Entity shortcut name', $input->getArgument('entity')), $input->getArgument('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->setArgument('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] = filter_var($fieldAttributes[$boolAttribute], FILTER_VALIDATE_BOOLEAN);
}
}
}
$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 overridden 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,90 @@
<?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
{
/**
* @var Generator
*/
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,64 @@
<?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;
public function __construct(Filesystem $filesystem)
{
$this->filesystem = $filesystem;
}
public function generate(BundleInterface $bundle, $name)
{
$bundleDir = $bundle->getPath();
$commandDir = $bundleDir.'/Command';
self::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 string 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,196 @@
<?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;
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($action['name'], 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')) {
self::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,316 @@
<?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;
/**
* @param Filesystem $filesystem
* @param string $rootDir
*/
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 bool $needWriteActions Whether or not to generate write actions
* @param bool $forceOverwrite Whether or not to overwrite the controller
*
* @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;
$entity = str_replace('\\', '/', $entity);
$entityParts = explode('/', $entity);
$entityName = end($entityParts);
$this->entitySingularized = lcfirst(Inflector::singularize($entityName));
$this->entityPluralized = lcfirst(Inflector::pluralize($entityName));
$this->bundle = $bundle;
$this->metadata = $metadata;
$this->setFormat($format);
$this->generateControllerClass($forceOverwrite);
$dir = sprintf('%s/Resources/views/%s', $this->rootDir, strtolower($entity));
if (!file_exists($dir)) {
self::mkdir($dir);
}
$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,
'identifier' => $this->metadata->identifier[0],
));
}
/**
* 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,
'identifier' => $this->metadata->identifier[0],
'entity_class' => $entityClass,
'namespace' => $this->bundle->getNamespace(),
'entity_namespace' => $entityNamespace,
'format' => $this->format,
// BC with Symfony 2.7
'use_form_type_instance' => !method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix'),
));
}
/**
* 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(str_replace('\\', '', $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
);
self::mkdir(dirname($entityPath));
self::dump($entityPath, $entityCode);
if ($mappingPath) {
self::mkdir(dirname($mappingPath));
self::dump($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,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\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),
'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)),
// BC with Symfony 2.7
'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,108 @@
<?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\Console\Output\ConsoleOutput;
/**
* Generator is the base class for all generators.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Generator
{
private $skeletonDirs;
private static $output;
/**
* 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);
}
/**
* Gets 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)
{
self::mkdir(dirname($target));
return self::dump($target, $this->render($template, $parameters));
}
/**
* @internal
*/
public static function mkdir($dir, $mode = 0777, $recursive = true)
{
if (!is_dir($dir)) {
mkdir($dir, $mode, $recursive);
self::writeln(sprintf(' <fg=green>created</> %s', self::relativizePath($dir)));
}
}
/**
* @internal
*/
public static function dump($filename, $content)
{
if (file_exists($filename)) {
self::writeln(sprintf(' <fg=yellow>updated</> %s', self::relativizePath($filename)));
} else {
self::writeln(sprintf(' <fg=green>created</> %s', self::relativizePath($filename)));
}
return file_put_contents($filename, $content);
}
private static function writeln($message)
{
if (null === self::$output) {
self::$output = new ConsoleOutput();
}
self::$output->writeln($message);
}
private static function relativizePath($absolutePath)
{
$relativePath = str_replace(getcwd(), '.', $absolutePath);
return is_dir($absolutePath) ? rtrim($relativePath, '/').'/' : $relativePath;
}
}

View file

@ -0,0 +1,19 @@
Copyright (c) 2011-2017 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,111 @@
<?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\Generator\Generator;
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;
/**
* @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 === Generator::dump($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,125 @@
<?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;
use Sensio\Bundle\GeneratorBundle\Generator\Generator;
/**
* Changes the PHP code of a Kernel.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class KernelManipulator extends Manipulator
{
protected $kernel;
protected $reflected;
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 Whether the operation succeeded
*
* @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)
);
Generator::dump($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 string|null
*/
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 string|null
*/
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,141 @@
<?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 Sensio\Bundle\GeneratorBundle\Generator\Generator;
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;
/**
* @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 Whether the operation succeeded
*
* @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))) {
Generator::mkdir($dir);
}
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 === Generator::dump($this->file, $code)) {
return false;
}
return true;
}
/**
* Checks if the routing file contains 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;
}
/**
* Adds 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,12 @@
SensioGeneratorBundle
=====================
**WARNING**: This bundle does not support Symfony 4. It also does not support the new bundle-less directory structure as created by Symfony Flex. Use the [Maker bundle](https://github.com/symfony/maker-bundle) instead.
The `SensioGeneratorBundle` extends the default Symfony 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,103 @@
Generating a New Bundle Skeleton
================================
.. caution::
If your application is based on Symfony 2.x version, replace ``php bin/console``
with ``php app/console`` before executing any of the console commands included
in this article.
Usage
-----
The ``generate:bundle`` generates a new bundle structure and automatically
activates it in the application.
By default the command is run in the interactive mode and asks questions to
determine the bundle name, location, configuration format and default
structure:
.. code-block:: bash
$ php bin/console generate:bundle
To deactivate the interactive mode, use the `--no-interaction` option but don't
forget to pass all needed options:
.. code-block:: bash
$ php bin/console generate:bundle --namespace=Acme/Bundle/BlogBundle --no-interaction
.. caution::
If the ``generate:bundle`` command returns an error about registering the
bundle namespace in ``composer.json``, add the following line to your
``composer.json`` file within the ``psr-4`` section:
``"Acme\\Bundle\\BlogBundle\\": "src/Acme/Bundle/BlogBundle"``
(for example:
.. code-block:: json
"autoload": {
"psr-4": {
"Acme\\Bundle\\BlogBundle\\": "src/Acme/Bundle/BlogBundle"
},
"classmap": [ "app/AppKernel.php", "app/AppCache.php" ]
},
), then execute the following command to regenerate the autoload files:
.. code-block:: bash
$ composer dump-autoload
Available Options
-----------------
``--shared``
Provide this option if you are creating a bundle that will be shared across
several of your applications or if you are developing a third-party bundle.
Don't set this option if you are developing a bundle that will be used
solely in your application (e.g. ``AppBundle``).
``--namespace``
The namespace of the bundle to create. 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 Bundle as a suffix):
.. code-block:: bash
$ php bin/console generate:bundle --namespace=Acme/Bundle/BlogBundle
``--bundle-name``
The optional bundle name. It must be a string ending with the ``Bundle``
suffix:
.. code-block:: bash
$ php bin/console generate:bundle --bundle-name=AcmeBlogBundle
``--dir``
The directory in which to store the bundle. By convention, the command
detects and uses the application's ``src/`` folder:
.. code-block:: bash
$ php bin/console generate:bundle --dir=/var/www/myproject/src
``--format``
**allowed values**: ``annotation|php|yml|xml`` **default**: ``annotation``
Determine the format to use for the generated configuration files (like
routing). By default, the command uses the ``annotation`` format (choosing
the ``annotation`` format expects the `SensioFrameworkExtraBundle`_ to
be installed):
.. code-block:: bash
$ php bin/console generate:bundle --format=annotation
.. _`SensioFrameworkExtraBundle`: http://symfony.com/doc/master/bundles/SensioFrameworkExtraBundle/index.html

View file

@ -0,0 +1,34 @@
Generating a New Command
========================
.. caution::
If your application is based on Symfony 2.x version, replace ``php bin/console``
with ``php app/console`` before executing any of the console commands included
in this article.
Usage
-----
The ``generate:command`` command generates a new Command class for the given
console command.
By default the command is run in the interactive mode and asks questions to
determine the bundle and the command name:
.. code-block:: bash
$ php bin/console generate:command
The command can be run in a non interactive mode by using the
``--no-interaction`` and providing the needed arguments:
.. code-block:: bash
$ php bin/console generate:command --no-interaction AcmeBlogBundle blog:publish-posts
Available Arguments
-------------------
* ``bundle``: The name of the bundle where the command class is generated.
* ``name``: The name of the command as you type it in the console.

View file

@ -0,0 +1,73 @@
Generating a New Controller
===========================
.. caution::
If your application is based on Symfony 2.x version, replace ``php bin/console``
with ``php app/console`` before executing any of the console commands included
in this article.
Usage
-----
The ``generate:controller`` command generates a new controller including
actions, tests, templates and routing.
By default, the command is run in the interactive mode and asks questions to
determine the bundle name, location, configuration format and default
structure:
.. code-block:: bash
$ php bin/console generate:controller
The command can be run in a non-interactive mode by using the ``--no-interaction``
option without forgetting all needed options:
.. code-block:: bash
$ php bin/console generate:controller --no-interaction --controller=AcmeBlogBundle:Post
Available Options
-----------------
``--controller``
The controller name given as a shortcut notation containing the bundle
name in which the controller is located and the name of the controller
(for instance, ``AcmeBlogBundle:Post`` creates a ``PostController`` class
inside the ``AcmeBlogBundle`` bundle):
.. code-block:: bash
$ php bin/console generate:controller --controller=AcmeBlogBundle:Post
``--actions``
The list of actions to generate in the controller class. This has a format
like ``%actionname%:%route%:%template`` (where ``:%template%`` is optional):
.. code-block:: bash
$ php bin/console generate:controller --actions="showPostAction:/article/{id} getListAction:/_list-posts/{max}:AcmeBlogBundle:Post:list_posts.html.twig"
# or
$ php bin/console generate:controller --actions=showPostAction:/article/{id} --actions=getListAction:/_list-posts/{max}:AcmeBlogBundle:Post:list_posts.html.twig
``--route-format``
**allowed values**: ``annotation|php|yml|xml`` **default**: ``annotation``
This option determines the format to use for the routing configuration.
By default, the command uses the ``annotation`` format:
.. code-block:: bash
$ php bin/console generate:controller --route-format=annotation
``--template-format``
**allowed values**: ``php|twig`` **default**: ``twig``
This option determines the format to use for the templates. By default,
the command uses the ``twig`` format:
.. code-block:: bash
$ php bin/console generate:controller --template-format=twig

View file

@ -0,0 +1,106 @@
Generating a CRUD Controller Based on a Doctrine Entity
=======================================================
.. caution::
If your application is based on Symfony 2.x version, replace ``php bin/console``
with ``php app/console`` before executing any of the console commands included
in this article.
Usage
-----
The ``generate:doctrine:crud`` command generates a basic controller for a
given entity located in a given bundle. This controller allows to perform
the five basic operations on a model.
* Listing all records,
* Showing one given record identified by its primary key,
* Creating a new record,
* Editing an existing record,
* Deleting an existing record.
By default, the command is run in the interactive mode and asks questions to
determine the entity name, the route prefix or whether or not to generate write
actions:
.. code-block:: bash
$ php bin/console generate:doctrine:crud
To deactivate the interactive mode, use the ``--no-interaction`` option or its
alias ``-n``, but don't forget to pass all needed options:
.. code-block:: bash
$ php bin/console generate:doctrine:crud AcmeBlogBundle:Post -n --format=annotation --with-write
Arguments
---------
``entity``
The entity name given in shortcut notation containing the bundle name
in which the entity is located and the name of the entity (for example,
``AcmeBlogBundle:Post``):
.. code-block:: bash
$ php bin/console generate:doctrine:crud AcmeBlogBundle:Post
Available Options
-----------------
``--entity``
.. caution::
This option has been deprecated in version 3.0, and will be removed in 4.0.
Pass it as argument instead.
The entity name given in shortcut notation containing the bundle name
in which the entity is located and the name of the entity (for example,
``AcmeBlogBundle:Post``):
.. code-block:: bash
$ php bin/console generate:doctrine:crud --entity=AcmeBlogBundle:Post
``--route-prefix``
The prefix to use for each route that identifies an action:
.. code-block:: bash
$ php bin/console generate:doctrine:crud --route-prefix=acme_post
``--with-write``
**allowed values**: ``yes|no`` **default**: ``no``
Whether or not to generate the ``new``, ``create``, ``edit``, ``update``
and ``delete`` actions:
.. code-block:: bash
$ php bin/console generate:doctrine:crud --with-write
``--format``
**allowed values**: ``annotation|php|yml|xml`` **default**: ``annotation``
Determine the format to use for the generated configuration files (like,
for example, routing). By default, the command uses the ``annotation``
format. Choosing the ``annotation`` format expects the `SensioFrameworkExtraBundle`_
to be installed:
.. code-block:: bash
$ php bin/console generate:doctrine:crud --format=annotation
``--overwrite``
**allowed values**: ``yes|no`` **default**: ``no``
Whether or not to overwrite any existing files:
.. code-block:: bash
$ php bin/console generate:doctrine:crud --overwrite
.. _`SensioFrameworkExtraBundle`: http://symfony.com/doc/master/bundles/SensioFrameworkExtraBundle/index.html

View file

@ -0,0 +1,82 @@
Generating a New Doctrine Entity Stub
=====================================
.. caution::
If your application is based on Symfony 2.x version, replace ``php bin/console``
with ``php app/console`` before executing any of the console commands included
in this article.
Usage
-----
The ``generate:doctrine:entity`` command generates a new Doctrine entity stub
including the mapping definition and the class properties, getters and setters.
By default, the command is run in the interactive mode and asks questions to
determine the bundle name, location, configuration format and default structure:
.. code-block:: bash
$ php bin/console generate:doctrine:entity
To deactivate the interactive mode, use the ``--no-interaction`` option or its
alias ``-n``, but don't forget to pass all needed options:
.. code-block:: bash
$ php bin/console generate:doctrine:entity AcmeBlogBundle:Post -n --fields="title:string(100) body:text" --format=xml
Arguments
---------
``entity``
The entity name given as a shortcut notation containing the bundle name
in which the entity is located and the name of the entity (for example,
``AcmeBlogBundle:Post``):
.. code-block:: bash
$ php bin/console generate:doctrine:entity AcmeBlogBundle:Post
Available Options
-----------------
``--entity``
.. caution::
This option has been deprecated in version 3.0, and will be removed in 4.0.
Pass it as argument instead.
The entity name given as a shortcut notation containing the bundle name
in which the entity is located and the name of the entity (for example,
``AcmeBlogBundle:Post``):
.. code-block:: bash
$ php bin/console generate:doctrine:entity --entity=AcmeBlogBundle:Post
``--fields``
The list of fields to generate in the entity class:
.. code-block:: bash
$ php bin/console generate:doctrine:entity --fields="title:string(length=100 nullable=true unique=false) body:text ranking:decimal(precision=10 scale=0)"
.. versionadded:: 3.0
Ability to pass named options to fields was added in version 3.0.
Previously, only the ``string`` type was allowed to receive the length
value as argument. Available options are ``length``, ``nullable``,
``unique``, ``precision`` and ``scale``.
``--format``
**allowed values**: ``annotation|php|yml|xml`` **default**: ``annotation``
This option determines the format to use for the generated Doctrine entity
mapping configuration files. By default, the command uses the ``annotation``
format:
.. code-block:: bash
$ php bin/console generate:doctrine:entity --format=annotation

View file

@ -0,0 +1,31 @@
Generating a New Form Type Class Based on a Doctrine Entity
===========================================================
.. caution::
If your application is based on Symfony 2.x version, replace ``php bin/console``
with ``php app/console`` before executing any of the console commands included
in this article.
Usage
-----
The ``generate:doctrine:form`` command generates a basic form type class
by using the metadata mapping of a given entity class:
.. code-block:: bash
$ php bin/console generate:doctrine:form AcmeBlogBundle:Post
Required Arguments
------------------
``entity``
The entity name given in shortcut notation containing the bundle name
in which the entity is located and the name of the entity (``AcmeBlogBundle:Post``,
for example):
.. code-block:: bash
$ php bin/console generate:doctrine:form AcmeBlogBundle:Post

View file

@ -0,0 +1,128 @@
SensioGeneratorBundle
=====================
This bundle provides commands for scaffolding bundles, forms, controllers and
even CRUD-based backends. The boilerplate code provided by these code generators
will save you a large amount of time and work.
Installation
------------
Step 1: Download the Bundle
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Open a command console, enter your project directory and execute the
following command to download the latest stable version of this bundle:
.. code-block:: bash
$ composer require sensio/generator-bundle
Step 2: Enable the Bundle
~~~~~~~~~~~~~~~~~~~~~~~~~
Then, enable the bundle by adding it to the list of registered bundles for the
``dev`` environment in the ``app/AppKernel.php`` file of your project::
// app/AppKernel.php
// ...
class AppKernel extends Kernel
{
public function registerBundles()
{
if (in_array($this->getEnvironment(), array('dev', 'test'))) {
$bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
// ...
}
// ...
}
// ...
}
List of Available Commands
--------------------------
All the commands provided by this bundle can be run in interactive or
non-interactive mode. The interactive mode asks you some questions to configure
the command parameters that actually generate the code.
Read the following articles to learn how to use the new commands:
.. toctree::
:maxdepth: 1
commands/generate_bundle
commands/generate_command
commands/generate_controller
commands/generate_doctrine_crud
commands/generate_doctrine_entity
commands/generate_doctrine_form
Overriding Skeleton Templates
-----------------------------
.. versionadded:: 2.3
The possibility to override the skeleton templates was added in 2.3.
All generators use a template skeleton to generate files. By default, the
commands use templates provided by the bundle under its ``Resources/skeleton/``
directory.
You can define custom skeleton templates by creating the same directory and
file structure in the following locations (displayed from highest to lowest
priority):
* ``<BUNDLE_PATH>/Resources/SensioGeneratorBundle/skeleton/``
* ``app/Resources/SensioGeneratorBundle/skeleton/``
The ``<BUNDLE_PATH>`` value refers to the base path of the bundle where you are
scaffolding a controller, a form or a CRUD backend.
For instance, if you want to override the ``edit`` template for the CRUD
generator, create a ``crud/views/edit.html.twig.twig`` file under
``app/Resources/SensioGeneratorBundle/skeleton/``.
When overriding a template, have a look at the default templates to learn more
about the available templates, their paths and the variables they have access.
Instead of copy/pasting the original template to create your own, you can also
extend it and only override the relevant parts:
.. code-block:: jinja
{# app/Resources/SensioGeneratorBundle/skeleton/crud/actions/create.php.twig #}
{# notice the "skeleton" prefix here -- more about it below #}
{% extends "skeleton/crud/actions/create.php.twig" %}
{% block phpdoc_header %}
{{ parent() }}
*
* This is going to be inserted after the phpdoc title
* but before the annotations.
{% endblock phpdoc_header %}
Complex templates in the default skeleton are split into Twig blocks to allow
easy inheritance and to avoid copy/pasting large chunks of code.
In some cases, templates in the skeleton include other ones, like
in the ``crud/views/edit.html.twig.twig`` template for instance:
.. code-block:: jinja
{{ include('crud/views/others/record_actions.html.twig.twig') }}
If you have defined a custom template for this template, it is going to be
used instead of the default one. But you can explicitly include the original
skeleton template by prefixing its path with ``skeleton/`` like we did above:
.. code-block:: jinja
{{ include('skeleton/crud/views/others/record_actions.html.twig.twig') }}
You can learn more about this neat "trick" in the official `Twig documentation`_.
.. _`Twig documentation`: http://twig.sensiolabs.org/doc/recipes.html#overriding-a-template-that-also-extends-itself

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_singularized }} entity.
{% endblock phpdoc_method_header %}
*
{% block phpdoc_method_annotations %}
{% if 'annotation' == format %}
* @Route("/{{ '{' ~ identifier ~ '}' }}", 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_singularized }} entity.
*
* @param {{ entity_class }} ${{ entity_singularized }} The {{ entity_singularized }} 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('{{ identifier }}' => ${{ entity_singularized }}->get{{ identifier|capitalize }}())))
->setMethod('DELETE')
->getForm()
;
}
{% endblock form %}

View file

@ -0,0 +1,41 @@
/**
{% block phpdoc_method_header %}
* Displays a form to edit an existing {{ entity_singularized }} entity.
{% endblock phpdoc_method_header %}
*
{% block phpdoc_method_annotations %}
{% if 'annotation' == format %}
* @Route("/{{ '{' ~ identifier ~ '}' }}/edit", name="{{ route_name_prefix }}_edit")
* @Method({"GET", "POST"})
{% endif %}
{% endblock phpdoc_method_annotations %}
*/
{% block method_definition %}
public function editAction(Request $request, {{ entity_class }} ${{ entity_singularized }})
{% endblock method_definition %}
{
{% block method_body %}
$deleteForm = $this->createDeleteForm(${{ entity_singularized }});
{% if use_form_type_instance -%}
$editForm = $this->createForm(new {{ entity_singularized|capitalize }}Type(), ${{ entity_singularized }});
{% else -%}
$editForm = $this->createForm('{{ namespace }}\Form\{{ entity }}Type', ${{ entity_singularized }});
{% endif -%}
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('{{ route_name_prefix }}_edit', array('{{ identifier }}' => ${{ entity_singularized }}->get{{ identifier|capitalize }}()));
}
{% endblock method_body %}
{% block method_return %}
return $this->render('{{ entity|lower|replace({'\\': '/'}) }}/edit.html.twig', array(
'{{ entity_singularized }}' => ${{ entity_singularized }},
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
{% endblock method_return %}
}

View file

@ -0,0 +1,28 @@
/**
{% block phpdoc_method_header %}
* Lists all {{ entity_singularized }} entities.
{% endblock phpdoc_method_header %}
*
{% block phpdoc_method_annotations %}
{% if 'annotation' == format %}
* @Route("/", name="{{ route_name_prefix }}_index")
* @Method("GET")
{% endif %}
{% endblock phpdoc_method_annotations %}
*/
{% block method_definition %}
public function indexAction()
{% endblock method_definition %}
{
{% block method_body %}
$em = $this->getDoctrine()->getManager();
${{ entity_pluralized }} = $em->getRepository('{{ bundle }}:{{ entity }}')->findAll();
{% endblock method_body %}
{% block method_return %}
return $this->render('{{ entity|lower|replace({'\\': '/'}) }}/index.html.twig', array(
'{{ entity_pluralized }}' => ${{ entity_pluralized }},
));
{% endblock method_return %}
}

View file

@ -0,0 +1,47 @@
/**
{% block phpdoc_method_header %}
* Creates a new {{ entity_singularized }} entity.
{% endblock phpdoc_method_header %}
*
{% block phpdoc_method_annotations %}
{% if 'annotation' == format %}
* @Route("/new", name="{{ route_name_prefix }}_new")
* @Method({"GET", "POST"})
{% endif %}
{% endblock phpdoc_method_annotations %}
*/
{% block method_definition %}
public function newAction(Request $request)
{% endblock method_definition %}
{
{% block method_body %}
${{ entity_singularized }} = new {{ entity_singularized|capitalize }}();
{% if use_form_type_instance -%}
$form = $this->createForm(new {{ entity_singularized|capitalize }}Type(), ${{ entity_singularized }});
{% else -%}
$form = $this->createForm('{{ namespace }}\Form\{{ entity }}Type', ${{ entity_singularized }});
{% endif -%}
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist(${{ entity_singularized }});
$em->flush();
{% if 'show' in actions -%}
return $this->redirectToRoute('{{ route_name_prefix }}_show', array('{{ identifier }}' => ${{ entity_singularized }}->get{{ identifier|capitalize }}()));
{%- else -%}
return $this->redirectToRoute('{{ route_name_prefix }}_index'));
{%- endif %}
}
{% endblock method_body %}
{% block method_return %}
return $this->render('{{ entity|lower|replace({'\\': '/'}) }}/new.html.twig', array(
'{{ entity_singularized }}' => ${{ entity_singularized }},
'form' => $form->createView(),
));
{% endblock method_return %}
}

View file

@ -0,0 +1,32 @@
/**
{% block phpdoc_method_header %}
* Finds and displays a {{ entity_singularized }} entity.
{% endblock phpdoc_method_header %}
*
{% block phpdoc_method_annotations %}
{% if 'annotation' == format %}
* @Route("/{{ '{' ~ identifier ~ '}' }}", name="{{ route_name_prefix }}_show")
* @Method("GET")
{% endif %}
{% endblock phpdoc_method_annotations %}
*/
{% block method_definition %}
public function showAction({{ entity_class }} ${{ entity_singularized }})
{% endblock method_definition %}
{
{% block method_body %}
{% if 'delete' in actions %}
$deleteForm = $this->createDeleteForm(${{ entity_singularized }});
{% endif %}
{% endblock method_body %}
{% block method_return %}
return $this->render('{{ entity|lower|replace({'\\': '/'}) }}/show.html.twig', array(
'{{ entity_singularized }}' => ${{ entity_singularized }},
{% if 'delete' in actions %}
'delete_form' => $deleteForm->createView(),
{% endif %}
));
{% endblock method_return %}
}

View file

@ -0,0 +1,76 @@
<?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 %}
{% if 'index' in actions %}
$collection->add('{{ route_name_prefix }}_index', new Route(
'/',
array('_controller' => '{{ bundle }}:{{ entity }}:index'),
array(),
array(),
'',
array(),
array('GET')
));
{% endif %}
{% if 'show' in actions %}
$collection->add('{{ route_name_prefix }}_show', new Route(
'/{{ '{' ~ identifier ~ '}' }}/show',
array('_controller' => '{{ bundle }}:{{ entity }}:show'),
array(),
array(),
'',
array(),
array('GET')
));
{% endif %}
{% if 'new' in actions %}
$collection->add('{{ route_name_prefix }}_new', new Route(
'/new',
array('_controller' => '{{ bundle }}:{{ entity }}:new'),
array(),
array(),
'',
array(),
array('GET', 'POST')
));
{% endif %}
{% if 'edit' in actions %}
$collection->add('{{ route_name_prefix }}_edit', new Route(
'/{{ '{' ~ identifier ~ '}' }}/edit',
array('_controller' => '{{ bundle }}:{{ entity }}:edit'),
array(),
array(),
'',
array(),
array('GET', 'POST')
));
{% endif %}
{% if 'delete' in actions %}
$collection->add('{{ route_name_prefix }}_delete', new Route(
'/{{ '{' ~ identifier ~ '}' }}/delete',
array('_controller' => '{{ bundle }}:{{ entity }}:delete'),
array(),
array(),
'',
array(),
array('DELETE')
));
{% endif %}
{% endblock body %}
{% block return %}
return $collection;
{% endblock return %}

View file

@ -0,0 +1,35 @@
<?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="{{ route_name_prefix }}_index" path="/" methods="GET">
<default key="_controller">{{ bundle }}:{{ entity }}:index</default>
</route>
<route id="{{ route_name_prefix }}_show" path="/{{ '{' ~ identifier ~ '}' }}/show" methods="GET">
<default key="_controller">{{ bundle }}:{{ entity }}:show</default>
</route>
{% if 'new' in actions %}
<route id="{{ route_name_prefix }}_new" path="/new" methods="GET|POST">
<default key="_controller">{{ bundle }}:{{ entity }}:new</default>
</route>
{% endif %}
{% if 'edit' in actions %}
<route id="{{ route_name_prefix }}_edit" path="/{{ '{' ~ identifier ~ '}' }}/edit" methods="GET|POST">
<default key="_controller">{{ bundle }}:{{ entity }}:edit</default>
</route>
{% endif %}
{% if 'delete' in actions %}
<route id="{{ route_name_prefix }}_delete" path="/{{ '{' ~ identifier ~ '}' }}/delete" methods="DELETE">
<default key="_controller">{{ bundle }}:{{ entity }}:delete</default>
</route>
{% endif %}
{% endblock body %}
</routes>

View file

@ -0,0 +1,34 @@
{% if 'index' in actions %}
{{ route_name_prefix }}_index:
path: /
defaults: { _controller: "{{ bundle }}:{{ entity }}:index" }
methods: GET
{% endif %}
{% if 'show' in actions %}
{{ route_name_prefix }}_show:
path: /{{ '{' ~ identifier ~ '}' }}/show
defaults: { _controller: "{{ bundle }}:{{ entity }}:show" }
methods: GET
{% endif %}
{% if 'new' in actions %}
{{ route_name_prefix }}_new:
path: /new
defaults: { _controller: "{{ bundle }}:{{ entity }}:new" }
methods: [GET, POST]
{% endif %}
{% if 'edit' in actions %}
{{ route_name_prefix }}_edit:
path: /{{ '{' ~ identifier ~ '}' }}/edit
defaults: { _controller: "{{ bundle }}:{{ entity }}:edit" }
methods: [GET, POST]
{% endif %}
{% if 'delete' in actions %}
{{ route_name_prefix }}_delete:
path: /{{ '{' ~ identifier ~ '}' }}/delete
defaults: { _controller: "{{ bundle }}:{{ entity }}:delete" }
methods: DELETE
{% endif %}

View file

@ -0,0 +1,57 @@
<?php
namespace {{ namespace }}\Controller{{ entity_namespace ? '\\' ~ entity_namespace : '' }};
{% block use_statements %}
use {{ namespace }}\Entity\{{ entity }};
{% if ('new' in actions or 'edit' in actions) and use_form_type_instance %}
use {{ namespace }}\Form\{{ entity }}Type;
{% endif %}
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
{% if 'annotation' == format -%}
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
{%- endif %}
{% if 'new' in actions or 'edit' in actions or 'delete' in actions %}
use Symfony\Component\HttpFoundation\Request;
{%- endif %}
{% endblock use_statements %}
/**
{% block phpdoc_class_header %}
* {{ entity_singularized|capitalize }} controller.
{% endblock phpdoc_class_header %}
*
{% block phpdoc_class_annotations %}
{% if 'annotation' == format %}
* @Route("{{ route_prefix|default('/') }}")
{% endif %}
{% endblock phpdoc_class_annotations %}
*/
{% block class_definition %}
class {{ entity_class }}Controller extends Controller
{% endblock class_definition %}
{
{% block class_body %}
{%- if 'index' in actions %}
{%- include 'crud/actions/index.php.twig' %}
{%- endif %}
{%- if 'new' in actions %}
{%- include 'crud/actions/new.php.twig' %}
{%- endif %}
{%- if 'show' in actions %}
{%- include 'crud/actions/show.php.twig' %}
{%- endif %}
{%- if 'edit' in actions %}
{%- include 'crud/actions/edit.php.twig' %}
{%- endif %}
{%- if 'delete' in actions %}
{%- include 'crud/actions/delete.php.twig' %}
{%- endif %}
{% endblock class_body %}
}

View file

@ -0,0 +1,44 @@
public function testCompleteScenario()
{
// Create a new client to browse the application
$client = static::createClient();
// Create a new entry in the database
$crawler = $client->request('GET', '/{{ route_prefix }}/');
$this->assertEquals(200, $client->getResponse()->getStatusCode(), "Unexpected HTTP status code for GET /{{ route_prefix }}/");
$crawler = $client->click($crawler->selectLink('Create a new entry')->link());
// Fill in the form and submit it
$form = $crawler->selectButton('Create')->form(array(
'{{ form_type_name|lower }}[field_name]' => 'Test',
// ... other fields to fill
));
$client->submit($form);
$crawler = $client->followRedirect();
// Check data in the show view
$this->assertGreaterThan(0, $crawler->filter('td:contains("Test")')->count(), 'Missing element td:contains("Test")');
// Edit the entity
$crawler = $client->click($crawler->selectLink('Edit')->link());
$form = $crawler->selectButton('Update')->form(array(
'{{ form_type_name|lower }}[field_name]' => 'Foo',
// ... other fields to fill
));
$client->submit($form);
$crawler = $client->followRedirect();
// Check the element contains an attribute with value equals "Foo"
$this->assertGreaterThan(0, $crawler->filter('[value="Foo"]')->count(), 'Missing element [value="Foo"]');
// Delete the entity
$client->submit($crawler->selectButton('Delete')->form());
$crawler = $client->followRedirect();
// Check the entity has been delete on the list
$this->assertNotRegExp('/Foo/', $client->getResponse()->getContent());
}

View file

@ -0,0 +1,14 @@
public function testCompleteScenario()
{
// Create a new client to browse the application
$client = static::createClient();
// Go to the list view
$crawler = $client->request('GET', '/{{ route_prefix }}/');
$this->assertEquals(200, $client->getResponse()->getStatusCode(), "Unexpected HTTP status code for GET /{{ route_prefix }}/");
// Go to the show view
$crawler = $client->click($crawler->selectLink('show')->link());
$this->assertEquals(200, $client->getResponse()->getStatusCode(), "Unexpected HTTP status code");
}

View file

@ -0,0 +1,24 @@
<?php
namespace {{ namespace }}\Tests\Controller{{ entity_namespace ? '\\' ~ entity_namespace : '' }};
{% block use_statements %}
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
{% endblock use_statements %}
{% block class_definition %}
class {{ entity_class }}ControllerTest extends WebTestCase
{% endblock class_definition %}
{
{% block class_body %}
/*
{%- if 'new' in actions %}
{%- include 'crud/tests/others/full_scenario.php.twig' -%}
{%- else %}
{%- include 'crud/tests/others/short_scenario.php.twig' -%}
{%- endif %}
*/
{% endblock class_body %}
}

View file

@ -0,0 +1,17 @@
{% block extends %}
{{ "{% extends 'base.html.twig' %}" }}
{% endblock extends %}
{% block body %}
{{ "{% block body %}" }}
<h1>{{ entity_singularized|capitalize }} edit</h1>
{{ '{{ form_start(edit_form) }}' }}
{{ '{{ form_widget(edit_form) }}' }}
<input type="submit" value="Edit" />
{{ '{{ form_end(edit_form) }}' }}
{% set hide_edit, hide_delete = true, false %}
{{ include('crud/views/others/record_actions.html.twig.twig') -}}
{{ "{% endblock %}" }}
{% endblock body %}

View file

@ -0,0 +1,89 @@
{% block extends %}
{{ "{% extends 'base.html.twig' %}" }}
{% endblock extends %}
{% block body %}
{{ "{% block body %}" }}
<h1>{{ entity_pluralized|capitalize }} list</h1>
<table>
<thead>
<tr>
{%- for field, metadata in fields %}
<th>{{ field|capitalize }}</th>
{%- endfor %}
<th>Actions</th>
</tr>
</thead>
<tbody>
{{ '{% for ' ~ entity_singularized ~ ' in ' ~ entity_pluralized ~ ' %}' }}
<tr>
{%- for field, metadata in fields %}
{%- if loop.first and ('show' in actions) %}
<td><a href="{{ "{{ path('" ~ route_name_prefix ~ "_show', { '"~ identifier ~"': " ~ entity_singularized ~ "."~ identifier ~" }) }}" }}">{{ '{{ ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ ' }}' }}</a></td>
{%- elseif metadata.type in ['datetime'] %}
<td>{{ '{% if ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ ' %}{{ ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ '|date(\'Y-m-d H:i:s\') }}{% endif %}' }}</td>
{%- elseif metadata.type in ['date'] %}
<td>{{ '{% if ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ ' %}{{ ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ '|date(\'Y-m-d\') }}{% endif %}' }}</td>
{%- elseif metadata.type in ['time'] %}
<td>{{ '{% if ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ ' %}{{ ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ '|date(\'H:i:s\') }}{% endif %}' }}</td>
{%- elseif metadata.type in ['array'] %}
<td>{{ '{% if ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ ' %}{{ ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ '|join(\', \') }}{% endif %}' }}</td>
{%- elseif metadata.type in ['boolean'] %}
<td>{{ '{% if ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ ' %}Yes{% else %}No{% endif %}' }}</td>
{%- else %}
<td>{{ '{{ ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ ' }}' }}</td>
{%- endif %}
{%- if loop.last %}
<td>
<ul>
{%- for action in record_actions %}
<li>
<a href="{{ "{{ path('" ~ route_name_prefix ~ "_" ~ action ~ "', { '"~ identifier ~"': " ~ entity_singularized ~ "."~ identifier ~" }) }}" }}">{{ action }}</a>
</li>
{%- endfor %}
</ul>
</td>
{%- endif %}
{%- endfor %}
</tr>
{{ '{% endfor %}' }}
</tbody>
</table>
{% if 'new' in actions -%}
<ul>
<li>
<a href="{{ "{{ path('" ~ route_name_prefix ~ "_new') }}" }}">Create a new {{ entity_singularized }}</a>
</li>
</ul>
{%- endif %}
{{ "{% endblock %}" }}
{% endblock body %}

View file

@ -0,0 +1,17 @@
{% block extends %}
{{ "{% extends 'base.html.twig' %}" }}
{% endblock extends %}
{% block body %}
{{ "{% block body %}" }}
<h1>{{ entity_singularized|capitalize }} creation</h1>
{{ '{{ form_start(form) }}' }}
{{ '{{ form_widget(form) }}' }}
<input type="submit" value="Create" />
{{ '{{ form_end(form) }}' }}
{% set hide_edit, hide_delete = true, true %}
{{ include('crud/views/others/record_actions.html.twig.twig') -}}
{{ "{% endblock %}" }}
{% endblock body %}

View file

@ -0,0 +1,24 @@
<ul>
<li>
<a href="{{ "{{ path('" ~ route_name_prefix ~ "_index') }}" }}">Back to the list</a>
</li>
{%- if ('edit' in actions) and (not hide_edit) %}
<li>
<a href="{{ "{{ path('" ~ route_name_prefix ~ "_edit', { '"~ identifier ~"': " ~ entity_singularized ~ "."~ identifier ~" }) }}" }}">Edit</a>
</li>
{%- endif %}
{%- if ('delete' in actions) and (not hide_delete) %}
<li>
{{ '{{ form_start(delete_form) }}' }}
<input type="submit" value="Delete">
{{ '{{ form_end(delete_form) }}' }}
</li>
{%- endif %}
</ul>

View file

@ -0,0 +1,52 @@
{% block extends %}
{{ "{% extends 'base.html.twig' %}" }}
{% endblock extends %}
{% block body %}
{{ "{% block body %}" }}
<h1>{{ entity_singularized|capitalize }}</h1>
<table>
<tbody>
{%- for field, metadata in fields %}
<tr>
<th>{{ field|capitalize }}</th>
{%- if metadata.type in ['datetime'] %}
<td>{{ '{% if ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ ' %}{{ ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ '|date(\'Y-m-d H:i:s\') }}{% endif %}' }}</td>
{%- elseif metadata.type in ['date'] %}
<td>{{ '{% if ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ ' %}{{ ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ '|date(\'Y-m-d\') }}{% endif %}' }}</td>
{%- elseif metadata.type in ['time'] %}
<td>{{ '{% if ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ ' %}{{ ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ '|date(\'H:i:s\') }}{% endif %}' }}</td>
{%- elseif metadata.type in ['array'] %}
<td>{{ '{% if ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ ' %}{{ ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ '|join(\', \') }}{% endif %}' }}</td>
{%- elseif metadata.type in ['boolean'] %}
<td>{{ '{% if ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ ' %}Yes{% else %}No{% endif %}' }}</td>
{%- else %}
<td>{{ '{{ ' ~ entity_singularized ~ '.' ~ field|replace({'_': ''}) ~ ' }}' }}</td>
{%- endif %}
</tr>
{%- endfor %}
</tbody>
</table>
{% set hide_edit, hide_delete = false, false %}
{{ include('crud/views/others/record_actions.html.twig.twig') -}}
{{ "{% endblock %}" }}
{% endblock body %}

View file

@ -0,0 +1,61 @@
<?php
namespace {{ namespace }}\Form{{ entity_namespace ? '\\' ~ entity_namespace : '' }};
{% block use_statements %}
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
{% endblock use_statements %}
{% block class_definition %}
class {{ form_class }} extends AbstractType
{% endblock class_definition %}
{
{% block class_body %}
{%- if fields|length > 0 %}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
{%- for field in fields -%}
->add('{{ field }}')
{%- endfor %};
}
{%- endif -%}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => '{{ namespace }}\Entity{{ entity_namespace ? '\\' ~ entity_namespace : '' }}\{{ entity_class }}'
));
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return '{{ form_type_name }}';
}
{# BC with Symfony 2.7 #}
{%- if get_name_required %}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
{% endif %}
{% endblock class_body %}
}

View file

@ -0,0 +1,23 @@
<?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\GeneratorBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* SensioGeneratorBundle.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SensioGeneratorBundle extends Bundle
{
}

View file

@ -0,0 +1,98 @@
<?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\Tests\Command\AutoComplete;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\GeneratorBundle\Command\AutoComplete\EntitiesAutoCompleter;
class EntitiesAutoCompleterTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getNamespaces
*/
public function testSuggestions($expected, $alias, $classes)
{
$autoCompleter = new EntitiesAutoCompleter($this->getEntityManagerMock($alias, $classes));
$this->assertSame($expected, $autoCompleter->getSuggestions());
}
public function getNamespaces()
{
return array(
array(
array('AcmeBlogBundle:Post'),
array('AcmeBlogBundle' => 'Acme\Bundle\BlogBundle\Entity'),
array('Acme\Bundle\BlogBundle\Entity\Post'),
),
array(
array('AcmeBlogBundle:Blog\Post'),
array('AcmeBlogBundle' => 'Acme\Bundle\BlogBundle\Entity'),
array('Acme\Bundle\BlogBundle\Entity\Blog\Post'),
),
array(
array(
'AcmeBlogBundle:Post',
'AcmeCommentBundle:Comment',
'AcmeBlogBundle:Blog\Post',
),
array(
'AcmeBlogBundle' => 'Acme\Bundle\BlogBundle\Entity',
'AcmeCommentBundle' => 'Acme\Bundle\CommentBundle\Entity',
),
array(
'Acme\Bundle\BlogBundle\Entity\Post',
'Acme\Bundle\CommentBundle\Entity\Comment',
'Acme\Bundle\BlogBundle\Entity\Blog\Post',
),
),
);
}
/**
* @param $aliases
* @param $classes
*
* @return EntityManagerInterface
*/
protected function getEntityManagerMock($aliases, $classes)
{
$cache = $this->getMockBuilder('Doctrine\Common\Persistence\Mapping\Driver\MappingDriver')->getMock();
$cache
->expects($this->any())
->method('getAllClassNames')
->will($this->returnValue($classes))
;
$configuration = $this->getMockBuilder('Doctrine\ORM\Configuration')->getMock();
$configuration
->expects($this->any())
->method('getMetadataDriverImpl')
->will($this->returnValue($cache))
;
$configuration
->expects($this->any())
->method('getEntityNamespaces')
->will($this->returnValue($aliases))
;
$manager = $this->getMockBuilder('Doctrine\ORM\EntityManagerInterface')->getMock();
$manager
->expects($this->any())
->method('getConfiguration')
->will($this->returnValue($configuration))
;
return $manager;
}
}

View file

@ -0,0 +1,149 @@
<?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\Tests\Command;
use Sensio\Bundle\GeneratorBundle\Model\Bundle;
use Symfony\Component\Console\Tester\CommandTester;
class GenerateBundleCommandTest extends GenerateCommandTest
{
/**
* @dataProvider getInteractiveCommandData
*/
public function testInteractiveCommand($options, $input, $expected)
{
list($namespace, $bundleName, $dir, $format, $shared) = $expected;
$bundle = new Bundle($namespace, $bundleName, $dir, $format, $shared);
$container = $this->getContainer();
// not shared? the tests should be at the root of the project
if (!$shared) {
$bundle->setTestsDirectory($container->getParameter('kernel.root_dir').'/../tests/'.$bundleName);
}
$generator = $this->getGenerator();
$generator
->expects($this->once())
->method('generateBundle')
->with($bundle)
;
$tester = new CommandTester($command = $this->getCommand($generator, $container));
$this->setInputs($tester, $command, $input);
$tester->execute($options);
}
public function getInteractiveCommandData()
{
$tmp = sys_get_temp_dir();
return array(
array(
array('--shared' => true, '--dir' => $tmp, '--format' => 'annotation'),
// shared, namespace, bundle name, directory, format
"\nFoo/BarBundle\n\n\n\n",
array('Foo\BarBundle', 'FooBarBundle', $tmp.'/', 'annotation', true),
),
array(
array(),
// shared, namespace, bundle name, directory, format
"y\nFoo/BarBundle\nBarBundle\nfoo\nyml",
array('Foo\BarBundle', 'BarBundle', 'foo/', 'yml', true),
),
array(
array('--shared' => true, '--dir' => $tmp, '--format' => 'yml', '--bundle-name' => 'BarBundle'),
// shared, namespace, bundle name, directory, format
"\nFoo/BarBundle\n\n\n\n",
array('Foo\BarBundle', 'BarBundle', $tmp.'/', 'yml', true),
),
array(
array(),
// shared, namespace, bundle name, directory, format
"n\nBazBundle\n\nsrc\nannotation",
array('BazBundle', 'BazBundle', 'src/', 'annotation', false),
),
);
}
/**
* @dataProvider getNonInteractiveCommandData
*/
public function testNonInteractiveCommand($options, $expected)
{
list($namespace, $bundleName, $dir, $format, $shared) = $expected;
$bundle = new Bundle($namespace, $bundleName, $dir, $format, $shared);
$container = $this->getContainer();
// not shared? the tests should be at the root of the project
if (!$shared) {
$bundle->setTestsDirectory($container->getParameter('kernel.root_dir').'/../tests/'.$bundleName);
}
$generator = $this->getGenerator();
$generator
->expects($this->once())
->method('generateBundle')
->with($bundle)
;
$tester = new CommandTester($this->getCommand($generator, $container));
$tester->execute($options, array('interactive' => false));
}
public function getNonInteractiveCommandData()
{
$tmp = sys_get_temp_dir();
return array(
array(
array('--shared' => true, '--dir' => $tmp, '--namespace' => 'Foo/BarBundle'),
array('Foo\BarBundle', 'FooBarBundle', $tmp.'/', 'annotation', true),
),
array(
array('--shared' => true, '--dir' => $tmp, '--namespace' => 'Foo/BarBundle', '--format' => 'yml', '--bundle-name' => 'BarBundle'),
array('Foo\BarBundle', 'BarBundle', $tmp.'/', 'yml', true),
),
array(
array('--dir' => $tmp, '--namespace' => 'BazBundle', '--format' => 'yml', '--bundle-name' => 'BazBundle'),
array('BazBundle', 'BazBundle', $tmp.'/', 'yml', false),
),
);
}
protected function getCommand($generator, $container)
{
$command = $this
->getMockBuilder('Sensio\Bundle\GeneratorBundle\Command\GenerateBundleCommand')
->setMethods(array('checkAutoloader', 'updateKernel', 'updateRouting'))
->getMock()
;
$command->setContainer($container);
$command->setHelperSet($this->getHelperSet());
$command->setGenerator($generator);
return $command;
}
protected function getGenerator()
{
// get a noop generator
return $this
->getMockBuilder('Sensio\Bundle\GeneratorBundle\Generator\BundleGenerator')
->disableOriginalConstructor()
->setMethods(array('generateBundle'))
->getMock()
;
}
}

View file

@ -0,0 +1,161 @@
<?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\Tests\Command;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
use Sensio\Bundle\GeneratorBundle\Command\GenerateCommandCommand;
class GenerateCommandCommandTest extends GenerateCommandTest
{
protected $generator;
/**
* @dataProvider getInteractiveCommandData
*/
public function testInteractiveCommand($options, $input, $expected)
{
list($bundle, $name) = $expected;
$generator = $this->getGenerator();
$generator
->expects($this->once())
->method('generate')
->with($this->getBundle(), $name)
;
$tester = new CommandTester($command = $this->getCommand($generator));
$this->setInputs($tester, $command, $input);
$tester->execute($options);
}
public function getInteractiveCommandData()
{
return array(
array(
array(),
"FooBarBundle\napp:foo-bar\n",
array('FooBarBundle', 'app:foo-bar'),
),
array(
array('bundle' => 'FooBarBundle'),
"app:foo-bar\n",
array('FooBarBundle', 'app:foo-bar'),
),
array(
array('name' => 'app:foo-bar'),
"FooBarBundle\n",
array('FooBarBundle', 'app:foo-bar'),
),
array(
array('bundle' => 'FooBarBundle', 'name' => 'app:foo-bar'),
'',
array('FooBarBundle', 'app:foo-bar'),
),
);
}
/**
* @dataProvider getNonInteractiveCommandData
*/
public function testNonInteractiveCommand($options, $expected)
{
list($bundle, $name) = $expected;
$generator = $this->getGenerator();
$generator
->expects($this->once())
->method('generate')
->with($this->getBundle(), $name)
;
$tester = new CommandTester($command = $this->getCommand($generator));
$tester->execute($options, array('interactive' => false));
}
public function getNonInteractiveCommandData()
{
return array(
array(
array('bundle' => 'FooBarBundle', 'name' => 'app:my-command'),
array('FooBarBundle', 'app:my-command'),
),
);
}
protected function getCommand($generator)
{
$command = new GenerateCommandCommand();
$command->setContainer($this->getContainer());
$command->setHelperSet($this->getHelperSet());
$command->setGenerator($generator);
return $command;
}
protected function getApplication($input = '')
{
$application = new Application();
$command = new GenerateCommandCommand();
$command->setContainer($this->getContainer());
$command->setHelperSet($this->getHelperSet($input));
$command->setGenerator($this->getGenerator());
$application->add($command);
return $application;
}
protected function getGenerator()
{
if (null === $this->generator) {
$this->setGenerator();
}
return $this->generator;
}
protected function setGenerator()
{
// get a noop generator
$this->generator = $this
->getMockBuilder('Sensio\Bundle\GeneratorBundle\Generator\CommandGenerator')
->disableOriginalConstructor()
->setMethods(array('generate'))
->getMock()
;
}
protected function getBundle()
{
if (null == $this->bundle) {
$this->setBundle();
}
return $this->bundle;
}
protected function setBundle()
{
$bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\BundleInterface')->getMock();
$bundle->expects($this->any())->method('getPath')->will($this->returnValue(''));
$bundle->expects($this->any())->method('getName')->will($this->returnValue('FooBarBundle'));
$bundle->expects($this->any())->method('getNamespace')->will($this->returnValue('Foo\BarBundle'));
$this->bundle = $bundle;
}
}

View file

@ -0,0 +1,101 @@
<?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\Tests\Command;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Filesystem\Filesystem;
use Sensio\Bundle\GeneratorBundle\Command\Helper\QuestionHelper;
use Symfony\Component\DependencyInjection\Container;
abstract class GenerateCommandTest extends \PHPUnit_Framework_TestCase
{
protected $bundle;
protected function tearDown()
{
if (null !== $this->bundle) {
$fs = new Filesystem();
$fs->remove($this->bundle->getPath());
}
}
protected function getHelperSet()
{
return new HelperSet(array(new FormatterHelper(), new QuestionHelper()));
}
protected function setInputs($tester, $command, $input)
{
$input .= str_repeat("\n", 10);
if (method_exists($tester, 'setInputs')) {
$tester->setInputs(explode("\n", $input));
} else {
$stream = fopen('php://memory', 'r+', false);
fwrite($stream, $input);
rewind($stream);
$command->getHelperSet()->get('question')->setInputStream($stream);
}
}
protected function getBundle()
{
if (null !== $this->bundle) {
return $this->bundle;
}
$tmpDir = sys_get_temp_dir().'/sf'.mt_rand(111111, 999999);
@mkdir($tmpDir, 0777, true);
$this->bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\BundleInterface')->getMock();
$this->bundle
->expects($this->any())
->method('getPath')
->will($this->returnValue($tmpDir))
;
return $this->bundle;
}
protected function getContainer()
{
$bundle = $this->getBundle();
$kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock();
$kernel
->expects($this->any())
->method('getBundle')
->will($this->returnValue($bundle))
;
$kernel
->expects($this->any())
->method('getBundles')
->will($this->returnValue(array($bundle)))
;
$filesystem = $this->getMockBuilder('Symfony\Component\Filesystem\Filesystem')->getMock();
$filesystem
->expects($this->any())
->method('isAbsolutePath')
->will($this->returnValue(true))
;
$container = new Container();
$container->set('kernel', $kernel);
$container->set('filesystem', $filesystem);
$container->setParameter('kernel.root_dir', $bundle->getPath());
return $container;
}
}

View file

@ -0,0 +1,196 @@
<?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\Tests\Command;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
use Sensio\Bundle\GeneratorBundle\Command\GenerateControllerCommand;
class GenerateControllerCommandTest extends GenerateCommandTest
{
protected $generator;
/**
* @dataProvider getInteractiveCommandData
*/
public function testInteractiveCommand($options, $input, $expected)
{
list($controller, $routeFormat, $templateFormat, $actions) = $expected;
$generator = $this->getGenerator();
$generator
->expects($this->once())
->method('generate')
->with($this->getBundle(), $controller, $routeFormat, $templateFormat, $actions)
;
$tester = new CommandTester($command = $this->getCommand($generator));
$this->setInputs($tester, $command, $input);
$tester->execute($options);
}
public function getInteractiveCommandData()
{
return array(
array(array(), "AcmeBlogBundle:Post\n", array('Post', 'annotation', 'twig', array())),
array(array('--controller' => 'AcmeBlogBundle:Post'), '', array('Post', 'annotation', 'twig', array())),
array(array(), "AcmeBlogBundle:Post\nyml\nphp\n", array('Post', 'yml', 'php', array())),
array(array(), "AcmeBlogBundle:Post\nyml\nphp\nshowAction\n\n\ngetListAction\n/_getlist/{max}\nAcmeBlogBundle:Lists:post.html.php\n", array('Post', 'yml', 'php', array(
'showAction' => array(
'name' => 'showAction',
'route' => '/show',
'placeholders' => array(),
'template' => 'AcmeBlogBundle:Post:show.html.php',
),
'getListAction' => array(
'name' => 'getListAction',
'route' => '/_getlist/{max}',
'placeholders' => array('max'),
'template' => 'AcmeBlogBundle:Lists:post.html.php',
),
))),
array(array('--route-format' => 'xml', '--template-format' => 'php', '--actions' => array('showAction:/{slug}:AcmeBlogBundle:article.html.php')), 'AcmeBlogBundle:Post', array('Post', 'xml', 'php', array(
'showAction' => array(
'name' => 'showAction',
'route' => '/{slug}',
'placeholders' => array('slug'),
'template' => 'AcmeBlogBundle:article.html.php',
),
))),
);
}
/**
* @dataProvider getNonInteractiveCommandData
*/
public function testNonInteractiveCommand($options, $expected)
{
list($controller, $routeFormat, $templateFormat, $actions) = $expected;
$generator = $this->getGenerator();
$generator
->expects($this->once())
->method('generate')
->with($this->getBundle(), $controller, $routeFormat, $templateFormat, $actions)
;
$tester = new CommandTester($command = $this->getCommand($generator));
$tester->execute($options, array('interactive' => false));
}
public function getNonInteractiveCommandData()
{
return array(
array(array('--controller' => 'AcmeBlogBundle:Post'), array('Post', 'annotation', 'twig', array())),
array(array('--controller' => 'AcmeBlogBundle:Post', '--route-format' => 'yml', '--template-format' => 'php'), array('Post', 'yml', 'php', array())),
array(array('--controller' => 'AcmeBlogBundle:Post', '--actions' => array('showAction getListAction:/_getlist/{max}:AcmeBlogBundle:List:post.html.twig createAction:/admin/create')), array('Post', 'annotation', 'twig', array(
'showAction' => array(
'name' => 'showAction',
'route' => '/show',
'placeholders' => array(),
'template' => 'default',
),
'getListAction' => array(
'name' => 'getListAction',
'route' => '/_getlist/{max}',
'placeholders' => array('max'),
'template' => 'AcmeBlogBundle:List:post.html.twig',
),
'createAction' => array(
'name' => 'createAction',
'route' => '/admin/create',
'placeholders' => array(),
'template' => 'default',
),
))),
array(array('--controller' => 'AcmeBlogBundle:Post', '--route-format' => 'xml', '--template-format' => 'php', '--actions' => array('showAction::')), array('Post', 'xml', 'php', array(
'showAction' => array(
'name' => 'showAction',
'route' => '/show',
'placeholders' => array(),
'template' => 'default',
),
))),
);
}
protected function getCommand($generator)
{
$command = $this
->getMockBuilder('Sensio\Bundle\GeneratorBundle\Command\GenerateControllerCommand')
->setMethods(array('generateRouting'))
->getMock()
;
$command->setContainer($this->getContainer());
$command->setHelperSet($this->getHelperSet());
$command->setGenerator($generator);
return $command;
}
protected function getApplication($input = '')
{
$application = new Application();
$command = new GenerateControllerCommand();
$command->setContainer($this->getContainer());
$command->setHelperSet($this->getHelperSet($input));
$command->setGenerator($this->getGenerator());
$application->add($command);
return $application;
}
protected function getGenerator()
{
if (null == $this->generator) {
$this->setGenerator();
}
return $this->generator;
}
protected function setGenerator()
{
// get a noop generator
$this->generator = $this
->getMockBuilder('Sensio\Bundle\GeneratorBundle\Generator\ControllerGenerator')
->disableOriginalConstructor()
->setMethods(array('generate'))
->getMock()
;
}
protected function getBundle()
{
if (null == $this->bundle) {
$this->setBundle();
}
return $this->bundle;
}
protected function setBundle()
{
$bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\BundleInterface')->getMock();
$bundle->expects($this->any())->method('getPath')->will($this->returnValue(''));
$bundle->expects($this->any())->method('getName')->will($this->returnValue('FooBarBundle'));
$bundle->expects($this->any())->method('getNamespace')->will($this->returnValue('Foo\BarBundle'));
$this->bundle = $bundle;
}
}

View file

@ -0,0 +1,300 @@
<?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\Tests\Command;
use Symfony\Component\Console\Tester\CommandTester;
class GenerateDoctrineCrudCommandTest extends GenerateCommandTest
{
/**
* @dataProvider getInteractiveCommandData
*/
public function testInteractiveCommand($options, $input, $expected)
{
list($entity, $format, $prefix, $withWrite) = $expected;
$generator = $this->getGenerator();
$generator
->expects($this->once())
->method('generate')
->with($this->getBundle(), $entity, $this->getDoctrineMetadata(), $format, $prefix, $withWrite)
;
$tester = new CommandTester($command = $this->getCommand($generator));
$this->setInputs($tester, $command, $input);
$tester->execute($options);
}
public function getInteractiveCommandData()
{
return array(
array(array(), "AcmeBlogBundle:Blog/Post\n", array('Blog\\Post', 'annotation', 'blog_post', false)),
array(array(), "AcmeBlogBundle:Blog/Post\ny\nyml\nfoobar\n", array('Blog\\Post', 'yml', 'foobar', true)),
array(array(), "AcmeBlogBundle:Blog/Post\ny\nyml\n/foobar\n", array('Blog\\Post', 'yml', 'foobar', true)),
array(array('entity' => 'AcmeBlogBundle:Blog/Post'), "\ny\nyml\nfoobar\n", array('Blog\\Post', 'yml', 'foobar', true)),
array(array('entity' => 'AcmeBlogBundle:Blog/Post'), '', array('Blog\\Post', 'annotation', 'blog_post', false)),
array(array('entity' => 'AcmeBlogBundle:Blog/Post', '--format' => 'yml', '--route-prefix' => 'foo', '--with-write' => true), '', array('Blog\\Post', 'yml', 'foo', true)),
// Deprecated, to be removed in 4.0
array(array('--entity' => 'AcmeBlogBundle:Blog/Post'), '', array('Blog\\Post', 'annotation', 'blog_post', false)),
array(array('--entity' => 'AcmeBlogBundle:Blog/Post', '--format' => 'yml', '--route-prefix' => 'foo', '--with-write' => true), '', array('Blog\\Post', 'yml', 'foo', true)),
);
}
/**
* @dataProvider getNonInteractiveCommandData
*/
public function testNonInteractiveCommand($options, $expected)
{
list($entity, $format, $prefix, $withWrite) = $expected;
$generator = $this->getGenerator();
$generator
->expects($this->once())
->method('generate')
->with($this->getBundle(), $entity, $this->getDoctrineMetadata(), $format, $prefix, $withWrite)
;
$tester = new CommandTester($this->getCommand($generator));
$tester->execute($options, array('interactive' => false));
}
public function getNonInteractiveCommandData()
{
return array(
array(array('entity' => 'AcmeBlogBundle:Blog/Post'), array('Blog\\Post', 'annotation', 'blog_post', false)),
array(array('entity' => 'AcmeBlogBundle:Blog/Post', '--format' => 'yml', '--route-prefix' => 'foo', '--with-write' => true), array('Blog\\Post', 'yml', 'foo', true)),
// Deprecated, to be removed in 4.0
array(array('--entity' => 'AcmeBlogBundle:Blog/Post'), array('Blog\\Post', 'annotation', 'blog_post', false)),
array(array('--entity' => 'AcmeBlogBundle:Blog/Post', '--format' => 'yml', '--route-prefix' => 'foo', '--with-write' => true), array('Blog\\Post', 'yml', 'foo', true)),
);
}
public function testCreateCrudWithAnnotationInNonAnnotationBundle()
{
$rootDir = $this->getContainer()->getParameter('kernel.root_dir');
$routing = <<<DATA
acme_blog:
resource: "@AcmeBlogBundle/Resources/config/routing.xml"
prefix: /
DATA;
@mkdir($rootDir.'/config', 0777, true);
file_put_contents($rootDir.'/config/routing.yml', $routing);
$options = array();
$input = "AcmeBlogBundle:Blog/Post\ny\nannotation\n/foobar\n";
$expected = array('Blog\\Post', 'annotation', 'foobar', true);
list($entity, $format, $prefix, $withWrite) = $expected;
$generator = $this->getGenerator();
$generator
->expects($this->once())
->method('generate')
->with($this->getBundle(), $entity, $this->getDoctrineMetadata(), $format, $prefix, $withWrite)
;
$tester = new CommandTester($command = $this->getCommand($generator));
$this->setInputs($tester, $command, $input);
$tester->execute($options);
$this->assertContains('acme_blog_post:', file_get_contents($rootDir.'/config/routing.yml'));
}
public function testCreateCrudWithAnnotationInAnnotationBundle()
{
$rootDir = $this->getContainer()->getParameter('kernel.root_dir');
$routing = <<<DATA
acme_blog:
resource: "@AcmeBlogBundle/Controller/"
type: annotation
DATA;
@mkdir($rootDir.'/config', 0777, true);
file_put_contents($rootDir.'/config/routing.yml', $routing);
$options = array();
$input = "AcmeBlogBundle:Blog/Post\ny\nyml\n/foobar\n";
$expected = array('Blog\\Post', 'yml', 'foobar', true);
list($entity, $format, $prefix, $withWrite) = $expected;
$generator = $this->getGenerator();
$generator
->expects($this->once())
->method('generate')
->with($this->getBundle(), $entity, $this->getDoctrineMetadata(), $format, $prefix, $withWrite)
;
$tester = new CommandTester($command = $this->getCommand($generator));
$this->setInputs($tester, $command, $input);
$tester->execute($options);
$this->assertEquals($routing, file_get_contents($rootDir.'/config/routing.yml'));
}
public function testAddACrudWithOneAlreadyDefined()
{
$rootDir = $this->getContainer()->getParameter('kernel.root_dir');
$routing = <<<DATA
acme_blog:
resource: "@AcmeBlogBundle/Controller/OtherController.php"
type: annotation
DATA;
@mkdir($rootDir.'/config', 0777, true);
file_put_contents($rootDir.'/config/routing.yml', $routing);
$options = array();
$input = "AcmeBlogBundle:Blog/Post\ny\nannotation\n/foobar\n";
$expected = array('Blog\\Post', 'annotation', 'foobar', true);
list($entity, $format, $prefix, $withWrite) = $expected;
$generator = $this->getGenerator();
$generator
->expects($this->once())
->method('generate')
->with($this->getBundle(), $entity, $this->getDoctrineMetadata(), $format, $prefix, $withWrite)
;
$tester = new CommandTester($command = $this->getCommand($generator));
$this->setInputs($tester, $command, $input);
$tester->execute($options);
$expected = '@AcmeBlogBundle/Controller/PostController.php';
$this->assertContains($expected, file_get_contents($rootDir.'/config/routing.yml'));
}
protected function getCommand($generator)
{
$command = $this
->getMockBuilder('Sensio\Bundle\GeneratorBundle\Command\GenerateDoctrineCrudCommand')
->setMethods(array('getEntityMetadata'))
->getMock()
;
$command
->expects($this->any())
->method('getEntityMetadata')
->will($this->returnValue(array($this->getDoctrineMetadata())))
;
$command->setContainer($this->getContainer());
$command->setHelperSet($this->getHelperSet());
$command->setGenerator($generator);
$command->setFormGenerator($this->getFormGenerator());
return $command;
}
protected function getDoctrineMetadata()
{
return $this
->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadataInfo')
->disableOriginalConstructor()
->getMock()
;
}
protected function getGenerator()
{
// get a noop generator
return $this
->getMockBuilder('Sensio\Bundle\GeneratorBundle\Generator\DoctrineCrudGenerator')
->disableOriginalConstructor()
->setMethods(array('generate'))
->getMock()
;
}
protected function getFormGenerator()
{
return $this
->getMockBuilder('Sensio\Bundle\GeneratorBundle\Generator\DoctrineFormGenerator')
->disableOriginalConstructor()
->setMethods(array('generate'))
->getMock()
;
}
protected function getBundle()
{
$bundle = parent::getBundle();
$bundle
->expects($this->any())
->method('getName')
->will($this->returnValue('AcmeBlogBundle'))
;
return $bundle;
}
protected function getContainer()
{
$container = parent::getContainer();
$container->set('doctrine', $this->getDoctrine());
return $container;
}
protected function getDoctrine()
{
$cache = $this->getMockBuilder('Doctrine\Common\Persistence\Mapping\Driver\MappingDriver')->getMock();
$cache
->expects($this->any())
->method('getAllClassNames')
->will($this->returnValue(array('Acme\Bundle\BlogBundle\Entity\Post')))
;
$configuration = $this->getMockBuilder('Doctrine\ORM\Configuration')->getMock();
$configuration
->expects($this->any())
->method('getMetadataDriverImpl')
->will($this->returnValue($cache))
;
$configuration
->expects($this->any())
->method('getEntityNamespaces')
->will($this->returnValue(array('AcmeBlogBundle' => 'Acme\Bundle\BlogBundle\Entity')))
;
$manager = $this->getMockBuilder('Doctrine\ORM\EntityManagerInterface')->getMock();
$manager
->expects($this->any())
->method('getConfiguration')
->will($this->returnValue($configuration))
;
$registry = $this->getMockBuilder('Symfony\Bridge\Doctrine\RegistryInterface')->getMock();
$registry
->expects($this->any())
->method('getAliasNamespace')
->will($this->returnValue('Acme\Bundle\BlogBundle\Entity\Blog\Post'))
;
$registry
->expects($this->any())
->method('getManager')
->will($this->returnValue($manager))
;
return $registry;
}
}

View file

@ -0,0 +1,122 @@
<?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\Tests\Command;
use Sensio\Bundle\GeneratorBundle\Model\EntityGeneratorResult;
use Symfony\Component\Console\Tester\CommandTester;
use Sensio\Bundle\GeneratorBundle\Command\GenerateDoctrineEntityCommand;
class GenerateDoctrineEntityCommandTest extends GenerateCommandTest
{
/**
* @dataProvider getInteractiveCommandData
*/
public function testInteractiveCommand($options, $input, $expected)
{
list($entity, $format, $fields) = $expected;
$generator = $this->getGenerator();
$generator
->expects($this->once())
->method('generate')
->with($this->getBundle(), $entity, $format, $fields)
->willReturn(new EntityGeneratorResult('', '', ''))
;
$tester = new CommandTester($command = $this->getCommand($generator));
$this->setInputs($tester, $command, $input);
$tester->execute($options);
}
public function getInteractiveCommandData()
{
return array(
array(array(), "Acme2BlogBundle:Blog/Post\n", array('Blog\\Post', 'annotation', array())),
array(array('entity' => 'Acme2BlogBundle:Blog/Post'), '', array('Blog\\Post', 'annotation', array())),
array(array(), "Acme2BlogBundle:Blog/Post\nyml\n\n", array('Blog\\Post', 'yml', array())),
array(array(), "Acme2BlogBundle:Blog/Post\nyml\ncreated_by\n\n255\nfalse\nfalse\ndescription\ntext\nfalse\ntrue\nupdated_at\ndatetime\ntrue\nfalse\nrating\ndecimal\n5\n3\nfalse\nfalse\n\n", array('Blog\\Post', 'yml', array(
array('fieldName' => 'createdBy', 'type' => 'string', 'length' => 255, 'columnName' => 'created_by'),
array('fieldName' => 'description', 'type' => 'text', 'unique' => true, 'columnName' => 'description'),
array('fieldName' => 'updatedAt', 'type' => 'datetimetz', 'nullable' => true, 'columnName' => 'updated_at'),
array('fieldName' => 'rating', 'type' => 'decimal', 'precision' => 5, 'scale' => 3, 'columnName' => 'rating'),
))),
// Deprecated, to be removed in 4.0
array(array('--entity' => 'Acme2BlogBundle:Blog/Post'), '', array('Blog\\Post', 'annotation', array())),
);
}
/**
* @dataProvider getNonInteractiveCommandData
*/
public function testNonInteractiveCommand($options, $expected)
{
list($entity, $format, $fields) = $expected;
$generator = $this->getGenerator();
$generator
->expects($this->once())
->method('generate')
->with($this->getBundle(), $entity, $format, $fields)
->willReturn(new EntityGeneratorResult('', '', ''))
;
$generator
->expects($this->any())
->method('isReservedKeyword')
->will($this->returnValue(false))
;
$tester = new CommandTester($this->getCommand($generator));
$tester->execute($options, array('interactive' => false));
}
public function getNonInteractiveCommandData()
{
return array(
array(array('entity' => 'Acme2BlogBundle:Blog/Post'), array('Blog\\Post', 'annotation', array())),
array(array('entity' => 'Acme2BlogBundle:Blog/Post', '--format' => 'yml', '--fields' => 'created_by:string(255) updated_by:string(length=128 nullable=true) description:text rating:decimal(precision=7 scale=2)'), array('Blog\\Post', 'yml', array(
array('fieldName' => 'created_by', 'type' => 'string', 'length' => 255),
array('fieldName' => 'updated_by', 'type' => 'string', 'length' => 128, 'nullable' => true),
array('fieldName' => 'description', 'type' => 'text'),
array('fieldName' => 'rating', 'type' => 'decimal', 'precision' => 7, 'scale' => 2),
))),
// Deprecated, to be removed in 4.0
array(array('--entity' => 'Acme2BlogBundle:Blog/Post'), array('Blog\\Post', 'annotation', array())),
array(array('--entity' => 'Acme2BlogBundle:Blog/Post', '--format' => 'yml', '--fields' => 'created_by:string(255) updated_by:string(length=128 nullable=true) description:text rating:decimal(precision=7 scale=2)'), array('Blog\\Post', 'yml', array(
array('fieldName' => 'created_by', 'type' => 'string', 'length' => 255),
array('fieldName' => 'updated_by', 'type' => 'string', 'length' => 128, 'nullable' => true),
array('fieldName' => 'description', 'type' => 'text'),
array('fieldName' => 'rating', 'type' => 'decimal', 'precision' => 7, 'scale' => 2),
))),
);
}
protected function getCommand($generator)
{
$command = new GenerateDoctrineEntityCommand();
$command->setContainer($this->getContainer());
$command->setHelperSet($this->getHelperSet());
$command->setGenerator($generator);
return $command;
}
protected function getGenerator()
{
// get a noop generator
return $this
->getMockBuilder('Sensio\Bundle\GeneratorBundle\Generator\DoctrineEntityGenerator')
->disableOriginalConstructor()
->setMethods(array('generate', 'isReservedKeyword'))
->getMock()
;
}
}

View file

@ -0,0 +1,155 @@
<?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\Tests\Generator;
use Sensio\Bundle\GeneratorBundle\Generator\BundleGenerator;
use Sensio\Bundle\GeneratorBundle\Model\Bundle;
class BundleGeneratorTest extends GeneratorTest
{
public function testGenerateYaml()
{
$bundle = new Bundle('Foo\BarBundle', 'FooBarBundle', $this->tmpDir, 'yml', true);
$this->getGenerator()->generateBundle($bundle);
$files = array(
'FooBarBundle.php',
'Controller/DefaultController.php',
'Resources/views/Default/index.html.twig',
'Resources/config/routing.yml',
'Tests/Controller/DefaultControllerTest.php',
'Resources/config/services.yml',
'DependencyInjection/Configuration.php',
'DependencyInjection/FooBarExtension.php',
);
foreach ($files as $file) {
$this->assertTrue(file_exists($this->tmpDir.'/Foo/BarBundle/'.$file), sprintf('%s has been generated', $file));
}
$content = file_get_contents($this->tmpDir.'/Foo/BarBundle/FooBarBundle.php');
$this->assertContains('namespace Foo\\BarBundle', $content);
$content = file_get_contents($this->tmpDir.'/Foo/BarBundle/Controller/DefaultController.php');
$this->assertContains('public function indexAction', $content);
$this->assertNotContains('@Route("/hello/{name}"', $content);
$content = file_get_contents($this->tmpDir.'/Foo/BarBundle/Resources/views/Default/index.html.twig');
$this->assertContains('Hello World!', $content);
$content = file_get_contents($this->tmpDir.'/Foo/BarBundle/Resources/config/services.yml');
$this->assertContains('class: Foo\BarBundle\Example', $content);
}
public function testGenerateXml()
{
$bundle = new Bundle('Foo\BarBundle', 'FooBarBundle', $this->tmpDir, 'xml', true);
$this->getGenerator()->generateBundle($bundle);
$files = array(
'FooBarBundle.php',
'Controller/DefaultController.php',
'Resources/views/Default/index.html.twig',
'Resources/config/routing.xml',
'Tests/Controller/DefaultControllerTest.php',
'Resources/config/services.xml',
'DependencyInjection/Configuration.php',
'DependencyInjection/FooBarExtension.php',
);
foreach ($files as $file) {
$this->assertTrue(file_exists($this->tmpDir.'/Foo/BarBundle/'.$file), sprintf('%s has been generated', $file));
}
$content = file_get_contents($this->tmpDir.'/Foo/BarBundle/Resources/config/services.xml');
$this->assertContains('<service id="foo_bar.example" class="Foo\BarBundle\Example">', $content);
}
public function testGenerateAnnotation()
{
$bundle = new Bundle('Foo\BarBundle', 'FooBarBundle', $this->tmpDir, 'annotation', false);
$this->getGenerator()->generateBundle($bundle);
$this->assertFalse(file_exists($this->tmpDir.'/Foo/BarBundle/Resources/config/routing.yml'));
$this->assertFalse(file_exists($this->tmpDir.'/Foo/BarBundle/Resources/config/routing.xml'));
$content = file_get_contents($this->tmpDir.'/Foo/BarBundle/Controller/DefaultController.php');
$this->assertContains('@Route("/")', $content);
}
public function testDirIsFile()
{
$this->filesystem->mkdir($this->tmpDir.'/Foo');
$this->filesystem->touch($this->tmpDir.'/Foo/BarBundle');
$bundle = new Bundle('Foo\BarBundle', 'FooBarBundle', $this->tmpDir, 'yml', false);
try {
$this->getGenerator()->generateBundle($bundle);
$this->fail('An exception was expected!');
} catch (\RuntimeException $e) {
$this->assertEquals(sprintf('Unable to generate the bundle as the target directory "%s" exists but is a file.', realpath($this->tmpDir.'/Foo/BarBundle')), $e->getMessage());
}
}
public function testIsNotWritableDir()
{
$this->filesystem->mkdir($this->tmpDir.'/Foo/BarBundle');
$this->filesystem->chmod($this->tmpDir.'/Foo/BarBundle', 0444);
$bundle = new Bundle('Foo\BarBundle', 'FooBarBundle', $this->tmpDir, 'yml', false);
try {
$this->getGenerator()->generateBundle($bundle);
$this->fail('An exception was expected!');
} catch (\RuntimeException $e) {
$this->filesystem->chmod($this->tmpDir.'/Foo/BarBundle', 0777);
$this->assertEquals(sprintf('Unable to generate the bundle as the target directory "%s" is not writable.', realpath($this->tmpDir.'/Foo/BarBundle')), $e->getMessage());
}
}
public function testIsNotEmptyDir()
{
$this->filesystem->mkdir($this->tmpDir.'/Foo/BarBundle');
$this->filesystem->touch($this->tmpDir.'/Foo/BarBundle/somefile');
$bundle = new Bundle('Foo\BarBundle', 'FooBarBundle', $this->tmpDir, 'yml', false);
try {
$this->getGenerator()->generateBundle($bundle);
$this->fail('An exception was expected!');
} catch (\RuntimeException $e) {
$this->filesystem->chmod($this->tmpDir.'/Foo/BarBundle', 0777);
$this->assertEquals(sprintf('Unable to generate the bundle as the target directory "%s" is not empty.', realpath($this->tmpDir.'/Foo/BarBundle')), $e->getMessage());
}
}
public function testExistingEmptyDirIsFine()
{
$this->filesystem->mkdir($this->tmpDir.'/Foo/BarBundle');
$bundle = new Bundle('Foo\BarBundle', 'FooBarBundle', $this->tmpDir, 'yml', true);
$this->getGenerator()->generateBundle($bundle);
}
public function testAlternateTestsDirectory()
{
$bundle = new Bundle('Foo\BarBundle', 'FooBarBundle', $this->tmpDir, 'xml', true);
$bundle->setTestsDirectory($this->tmpDir.'/other/path/tests');
$this->getGenerator()->generateBundle($bundle);
$this->assertTrue(file_exists($this->tmpDir.'/other/path/tests/Controller/DefaultControllerTest.php'));
}
protected function getGenerator()
{
$generator = new BundleGenerator($this->filesystem);
$generator->setSkeletonDirs(__DIR__.'/../../Resources/skeleton');
return $generator;
}
}

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\Tests\Generator;
use Sensio\Bundle\GeneratorBundle\Generator\CommandGenerator;
class CommandGeneratorTest extends GeneratorTest
{
public function testGenerateController()
{
$commandName = 'app:foo-bar';
$commandFile = 'Command/AppFooBarCommand.php';
$commandPath = $this->tmpDir.'/'.$commandFile;
$this->getGenerator()->generate($this->getBundle(), $commandName);
$this->assertTrue(file_exists($commandPath), sprintf('%s file has been generated.', $commandFile));
$commandContent = file_get_contents($commandPath);
$strings = array(
'namespace Foo\\BarBundle\\Command',
'class AppFooBarCommand extends ContainerAwareCommand',
sprintf("->setName('%s')", $commandName),
);
foreach ($strings as $string) {
$this->assertContains($string, $commandContent);
}
}
/**
* @dataProvider getNames
*/
public function testClassify($commandName, $className)
{
$generator = $this->getGenerator();
$this->assertEquals($className, $generator->classify($commandName));
}
public function getNames()
{
return array(
array('app', 'App'),
array('app-foo', 'AppFoo'),
array('app_foo', 'AppFoo'),
array('app:foo-bar', 'AppFooBar'),
array('app:foo:bar', 'AppFooBar'),
array('app:foo:bar-baz', 'AppFooBarBaz'),
array('app:foo:bar_baz', 'AppFooBarBaz'),
array('app-foo:bar-baz:foo-bar', 'AppFooBarBazFooBar'),
);
}
protected function getGenerator()
{
$generator = new CommandGenerator($this->filesystem);
$generator->setSkeletonDirs(__DIR__.'/../../Resources/skeleton');
return $generator;
}
protected function getBundle()
{
$bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\BundleInterface')->getMock();
$bundle->expects($this->any())->method('getPath')->will($this->returnValue($this->tmpDir));
$bundle->expects($this->any())->method('getName')->will($this->returnValue('FooBarBundle'));
$bundle->expects($this->any())->method('getNamespace')->will($this->returnValue('Foo\BarBundle'));
return $bundle;
}
}

View file

@ -0,0 +1,139 @@
<?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\Tests\Generator;
use Sensio\Bundle\GeneratorBundle\Generator\ControllerGenerator;
class ControllerGeneratorTest extends GeneratorTest
{
public function testGenerateController()
{
$this->getGenerator()->generate($this->getBundle(), 'Welcome', 'annotation', 'twig');
$files = array(
'Controller/WelcomeController.php',
'Tests/Controller/WelcomeControllerTest.php',
);
foreach ($files as $file) {
$this->assertTrue(file_exists($this->tmpDir.'/'.$file), sprintf('%s has been generated', $file));
}
$content = file_get_contents($this->tmpDir.'/Controller/WelcomeController.php');
$strings = array(
'namespace Foo\\BarBundle\\Controller',
'class WelcomeController',
);
foreach ($strings as $string) {
$this->assertContains($string, $content);
}
$content = file_get_contents($this->tmpDir.'/Tests/Controller/WelcomeControllerTest.php');
$strings = array(
'namespace Foo\\BarBundle\\Tests\\Controller',
'class WelcomeControllerTest',
);
foreach ($strings as $string) {
$this->assertContains($string, $content);
}
}
public function testGenerateActions()
{
$generator = $this->getGenerator();
$actions = array(
0 => array(
'name' => 'showPageAction',
'route' => '/{id}/{slug}',
'placeholders' => array('id', 'slug'),
'template' => 'FooBarBundle:Page:show_page.html.twig',
),
1 => array(
'name' => 'getListOfPagesAction',
'route' => '/_get-pages/{max_count}',
'placeholders' => array('max_count'),
'template' => 'FooBarBundle:Page:pages_list.html.twig',
),
);
$generator->generate($this->getBundle(), 'Page', 'annotation', 'twig', $actions);
$files = array(
'Resources/views/Page/show_page.html.twig',
'Resources/views/Page/pages_list.html.twig',
);
foreach ($files as $file) {
$this->assertTrue(file_exists($this->tmpDir.'/'.$file), sprintf('%s has been generated', $file));
}
$content = file_get_contents($this->tmpDir.'/Controller/PageController.php');
$strings = array(
'public function showPageAction($id, $slug)',
'public function getListOfPagesAction($max_count)',
'return $this->render(\'FooBarBundle:Page:show_page.html.twig\', array(',
'return $this->render(\'FooBarBundle:Page:pages_list.html.twig\', array(',
);
foreach ($strings as $string) {
$this->assertContains($string, $content);
}
}
public function testGenerateActionsWithNonDefaultFormats()
{
$generator = $this->getGenerator();
$generator->generate($this->getBundle(), 'Page', 'yml', 'php', array(
1 => array(
'name' => 'showPageAction',
'route' => '/{slug}',
'placeholders' => array('slug'),
'template' => 'FooBarBundle:Page:showPage.html.php',
),
));
$files = array(
'Resources/views/Page/showPage.html.php',
'Resources/config/routing.yml',
);
foreach ($files as $file) {
$this->assertTrue(file_exists($this->tmpDir.'/'.$file), $file.' has been generated');
}
$content = file_get_contents($this->tmpDir.'/Controller/PageController.php');
$this->assertNotContains('@Route()', $content, 'Routing is done via a yml file');
$this->assertContains("return \$this->render('FooBarBundle:Page:showPage.html.php', array(", $content, 'Controller renders template');
$content = file_get_contents($this->tmpDir.'/Resources/views/Page/showPage.html.php');
$this->assertContains($this->getBundle()->getName().':Page:showPage', $content);
$content = file_get_contents($this->tmpDir.'/Resources/config/routing.yml');
$this->assertContains("show_page:\n path: /{slug}\n defaults: { _controller: FooBarBundle:Page:showPage }", $content);
}
protected function getGenerator()
{
$generator = new ControllerGenerator($this->filesystem);
$generator->setSkeletonDirs(__DIR__.'/../../Resources/skeleton');
return $generator;
}
protected function getBundle()
{
$bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\BundleInterface')->getMock();
$bundle->expects($this->any())->method('getPath')->will($this->returnValue($this->tmpDir));
$bundle->expects($this->any())->method('getName')->will($this->returnValue('FooBarBundle'));
$bundle->expects($this->any())->method('getNamespace')->will($this->returnValue('Foo\BarBundle'));
return $bundle;
}
}

View file

@ -0,0 +1,287 @@
<?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\Tests\Generator;
use Sensio\Bundle\GeneratorBundle\Generator\DoctrineCrudGenerator;
class DoctrineCrudGeneratorTest extends GeneratorTest
{
public function testGenerateYamlFull()
{
$this->getGenerator()->generate($this->getBundle(), 'Post', $this->getMetadata(), 'yml', '/post', true, true);
$files = array(
'Controller/PostController.php',
'Tests/Controller/PostControllerTest.php',
'Resources/config/routing/post.yml',
'Resources/views/post/index.html.twig',
'Resources/views/post/show.html.twig',
'Resources/views/post/new.html.twig',
'Resources/views/post/edit.html.twig',
);
foreach ($files as $file) {
$this->assertTrue(file_exists($this->tmpDir.'/'.$file), sprintf('%s has been generated', $file));
}
$files = array(
'Resources/config/routing/post.xml',
);
foreach ($files as $file) {
$this->assertFalse(file_exists($this->tmpDir.'/'.$file), sprintf('%s has not been generated', $file));
}
$content = file_get_contents($this->tmpDir.'/Controller/PostController.php');
$strings = array(
'namespace Foo\BarBundle\Controller;',
'public function indexAction',
'public function showAction',
'public function newAction',
'public function editAction',
);
foreach ($strings as $string) {
$this->assertContains($string, $content);
}
}
public function testGenerateXml()
{
$this->getGenerator()->generate($this->getBundle(), 'Post', $this->getMetadata(), 'xml', '/post', false, true);
$files = array(
'Controller/PostController.php',
'Tests/Controller/PostControllerTest.php',
'Resources/config/routing/post.xml',
'Resources/views/post/index.html.twig',
'Resources/views/post/show.html.twig',
);
foreach ($files as $file) {
$this->assertTrue(file_exists($this->tmpDir.'/'.$file), sprintf('%s has been generated', $file));
}
$files = array(
'Resources/config/routing/post.yml',
'Resources/views/post/new.html.twig',
'Resources/views/post/edit.html.twig',
);
foreach ($files as $file) {
$this->assertFalse(file_exists($this->tmpDir.'/'.$file), sprintf('%s has not been generated', $file));
}
$content = file_get_contents($this->tmpDir.'/Controller/PostController.php');
$strings = array(
'namespace Foo\BarBundle\Controller;',
'public function indexAction',
'public function showAction',
);
foreach ($strings as $string) {
$this->assertContains($string, $content);
}
$content = file_get_contents($this->tmpDir.'/Controller/PostController.php');
$strings = array(
'public function newAction',
'public function editAction',
'@Route',
);
foreach ($strings as $string) {
$this->assertNotContains($string, $content);
}
}
public function testGenerateAnnotationWrite()
{
$this->getGenerator()->generate($this->getBundle(), 'Post', $this->getMetadata(), 'annotation', '/post', true, true);
$files = array(
'Controller/PostController.php',
'Tests/Controller/PostControllerTest.php',
'Resources/views/post/index.html.twig',
'Resources/views/post/show.html.twig',
'Resources/views/post/new.html.twig',
'Resources/views/post/edit.html.twig',
);
foreach ($files as $file) {
$this->assertTrue(file_exists($this->tmpDir.'/'.$file), sprintf('%s has been generated', $file));
}
$files = array(
'Resources/config/routing/post.yml',
'Resources/config/routing/post.xml',
);
foreach ($files as $file) {
$this->assertFalse(file_exists($this->tmpDir.'/'.$file), sprintf('%s has not been generated', $file));
}
$content = file_get_contents($this->tmpDir.'/Controller/PostController.php');
$strings = array(
'namespace Foo\BarBundle\Controller;',
'public function indexAction',
'public function showAction',
'public function newAction',
'public function editAction',
'@Route',
);
foreach ($strings as $string) {
$this->assertContains($string, $content);
}
}
public function testGenerateAnnotation()
{
$this->getGenerator()->generate($this->getBundle(), 'Post', $this->getMetadata(), 'annotation', '/post', false, true);
$files = array(
'Controller/PostController.php',
'Tests/Controller/PostControllerTest.php',
'Resources/views/post/index.html.twig',
'Resources/views/post/show.html.twig',
);
foreach ($files as $file) {
$this->assertTrue(file_exists($this->tmpDir.'/'.$file), sprintf('%s has been generated', $file));
}
$files = array(
'Resources/config/routing/post.yml',
'Resources/config/routing/post.xml',
'Resources/views/post/new.html.twig',
'Resources/views/post/edit.html.twig',
);
foreach ($files as $file) {
$this->assertFalse(file_exists($this->tmpDir.'/'.$file), sprintf('%s has not been generated', $file));
}
$content = file_get_contents($this->tmpDir.'/Controller/PostController.php');
$strings = array(
'namespace Foo\BarBundle\Controller;',
'public function indexAction',
'public function showAction',
'@Route("/post")', // Controller level
'@Route("/", name="post_index")',
'@Route("/{id}", name="post_show")',
);
foreach ($strings as $string) {
$this->assertContains($string, $content);
}
$content = file_get_contents($this->tmpDir.'/Controller/PostController.php');
$strings = array(
'public function newAction',
'public function editAction',
);
foreach ($strings as $string) {
$this->assertNotContains($string, $content);
}
}
public function testGenerateNamespacedEntity()
{
$this->getGenerator()->generate($this->getBundle(), 'Blog\Post', $this->getMetadata(), 'annotation', '/blog_post', true, true);
$files = array(
'Controller/Blog/PostController.php',
'Tests/Controller/Blog/PostControllerTest.php',
'Resources/views/blog/post/index.html.twig',
'Resources/views/blog/post/show.html.twig',
'Resources/views/blog/post/new.html.twig',
'Resources/views/blog/post/edit.html.twig',
);
foreach ($files as $file) {
$this->assertTrue(file_exists($this->tmpDir.'/'.$file), sprintf('%s has been generated', $file));
}
$content = file_get_contents($this->tmpDir.'/Controller/Blog/PostController.php');
$strings = array(
'namespace Foo\BarBundle\Controller\Blog;',
'@Route("/blog_post")', // Controller level
'@Route("/", name="blog_post_index")',
'@Route("/{id}", name="blog_post_show")',
'@Route("/new", name="blog_post_new")',
'@Route("/{id}/edit", name="blog_post_edit")',
'@Route("/{id}", name="blog_post_delete")',
'public function showAction(Post $post)',
'\'post\' => $post,',
'\'posts\' => $posts,',
);
if (method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')) {
// Symfony >= 2.8
$strings[] = '$form = $this->createForm(\'Foo\BarBundle\Form\Blog\PostType\', $post);';
$strings[] = '$editForm = $this->createForm(\'Foo\BarBundle\Form\Blog\PostType\', $post);';
} else {
$strings[] = '$form = $this->createForm(new PostType(), $post);';
$strings[] = '$editForm = $this->createForm(new PostType(), $post);';
}
foreach ($strings as $string) {
$this->assertContains($string, $content);
}
}
/**
* @dataProvider getRoutePrefixes
*/
public function testGetRouteNamePrefix($original, $expected)
{
$prefix = DoctrineCrudGenerator::getRouteNamePrefix($original);
$this->assertEquals($expected, $prefix);
}
public function getRoutePrefixes()
{
return array(
array('', ''),
array('/', ''),
array('//', ''),
array('/{foo}', ''),
array('/{_foo}', ''),
array('/{/foo}', ''),
array('/{/foo/}', ''),
array('/{_locale}', ''),
array('/{_locale}/foo', 'foo'),
array('/{_locale}/foo/', 'foo'),
array('/{_locale}/foo/{_format}', 'foo'),
array('/{_locale}/foo/{_format}/', 'foo'),
array('/{_locale}/foo/{_format}/bar', 'foo_bar'),
array('/{_locale}/foo/{_format}/bar/', 'foo_bar'),
array('/{_locale}/foo/{_format}/bar//', 'foo_bar'),
array('/{foo}/foo/{bar}/bar', 'foo_bar'),
array('/{foo}/foo/{bar}/bar/', 'foo_bar'),
array('/{foo}/foo/{bar}/bar//', 'foo_bar'),
);
}
protected function getGenerator()
{
$generator = new DoctrineCrudGenerator($this->filesystem, $this->tmpDir);
$generator->setSkeletonDirs(__DIR__.'/../../Resources/skeleton');
return $generator;
}
protected function getBundle()
{
$bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\BundleInterface')->getMock();
$bundle->expects($this->any())->method('getPath')->will($this->returnValue($this->tmpDir));
$bundle->expects($this->any())->method('getName')->will($this->returnValue('FooBarBundle'));
$bundle->expects($this->any())->method('getNamespace')->will($this->returnValue('Foo\BarBundle'));
return $bundle;
}
public function getMetadata()
{
$metadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadataInfo')->disableOriginalConstructor()->getMock();
$metadata->identifier = array('id');
$metadata->fieldMappings = array('title' => array('type' => 'string'));
return $metadata;
}
}

View file

@ -0,0 +1,206 @@
<?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\Tests\Generator;
use Sensio\Bundle\GeneratorBundle\Generator\DoctrineEntityGenerator;
class DoctrineEntityGeneratorTest extends GeneratorTest
{
const FORMAT_XML = 'xml';
const FORMAT_YAML = 'yml';
const FORMAT_ANNOTATION = 'annotation';
const WITH_REPOSITORY = true;
const WITHOUT_REPOSITORY = false;
public function testGenerateYaml()
{
$this->generate(self::FORMAT_YAML);
$files = array(
'Entity/Foo.php',
'Resources/config/doctrine/Foo.orm.yml',
);
$this->assertFilesExists($files);
$this->assertAttributesAndMethodsExists();
}
public function testGenerateSubNamespacedYaml()
{
$this->generateSubNamespaced(self::FORMAT_YAML);
$files = array(
'Entity/Sub/Foo.php',
'Resources/config/doctrine/Sub.Foo.orm.yml',
);
$this->assertFilesExists($files);
$this->assertAttributesAndMethodsExists(array(), 'Sub/Foo');
}
public function testGenerateXml()
{
$this->generate(self::FORMAT_XML);
$files = array(
'Entity/Foo.php',
'Resources/config/doctrine/Foo.orm.xml',
);
$this->assertFilesExists($files);
$this->assertAttributesAndMethodsExists();
}
public function testGenerateSubNamespacedXml()
{
$this->generateSubNamespaced(self::FORMAT_XML);
$files = array(
'Entity/Sub/Foo.php',
'Resources/config/doctrine/Sub.Foo.orm.xml',
);
$this->assertFilesExists($files);
$this->assertAttributesAndMethodsExists(array(), 'Sub/Foo');
}
public function testGenerateAnnotation()
{
$this->generate(self::FORMAT_ANNOTATION);
$files = array(
'Entity/Foo.php',
);
$annotations = array(
'@ORM\Table(name="foo")',
'@ORM\Column(name="bar"',
'@ORM\Column(name="baz"',
);
$this->assertFilesExists($files);
$this->assertAttributesAndMethodsExists($annotations);
}
public function testGenerateSubNamespacedAnnotation()
{
$this->generateSubNamespaced(self::FORMAT_ANNOTATION);
$files = array(
'Entity/Sub/Foo.php',
);
$annotations = array(
'@ORM\Table(name="sub_foo")',
'@ORM\Column(name="bar"',
'@ORM\Column(name="baz"',
);
$this->assertFilesExists($files);
$this->assertAttributesAndMethodsExists($annotations, 'Sub/Foo');
}
protected function assertFilesExists(array $files)
{
foreach ($files as $file) {
$this->assertTrue(file_exists($this->tmpDir.'/'.$file), sprintf('%s has been generated', $file));
}
}
protected function assertAttributesAndMethodsExists(array $otherStrings = array(), $entity = 'Foo')
{
$content = file_get_contents($this->tmpDir.'/Entity/'.$entity.'.php');
$strings = array(
'namespace Foo\\BarBundle\\Entity',
'class Foo',
'private $id',
'private $bar',
'private $baz',
'public function getId',
'public function getBar',
'public function getBaz',
'public function setBar',
'public function setBaz',
);
$strings = array_merge($strings, $otherStrings);
foreach ($strings as $string) {
$this->assertContains($string, $content);
}
}
protected function generate($format)
{
$this->getGenerator()->generate($this->getBundle(), 'Foo', $format, $this->getFields());
}
protected function generateSubNamespaced($format)
{
$this->getGenerator()->generate($this->getBundle(), 'Sub\Foo', $format, $this->getFields());
}
protected function getGenerator()
{
$generator = new DoctrineEntityGenerator($this->filesystem, $this->getRegistry());
$generator->setSkeletonDirs(__DIR__.'/../../Resources/skeleton');
return $generator;
}
protected function getBundle()
{
$bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\BundleInterface')->getMock();
$bundle->expects($this->any())->method('getPath')->will($this->returnValue($this->tmpDir));
$bundle->expects($this->any())->method('getName')->will($this->returnValue('FooBarBundle'));
$bundle->expects($this->any())->method('getNamespace')->will($this->returnValue('Foo\BarBundle'));
return $bundle;
}
protected function getFields()
{
return array(
array('fieldName' => 'bar', 'type' => 'string', 'length' => 255),
array('fieldName' => 'baz', 'type' => 'integer', 'length' => 11),
);
}
public function getRegistry()
{
$registry = $this->getMockBuilder('Symfony\Bridge\Doctrine\RegistryInterface')->getMock();
$registry->expects($this->any())->method('getManager')->will($this->returnValue($this->getManager()));
$registry->expects($this->any())->method('getAliasNamespace')->will($this->returnValue('Foo\\BarBundle\\Entity'));
return $registry;
}
public function getManager()
{
$manager = $this->getMockBuilder('Doctrine\ORM\EntityManagerInterface')->getMock();
$manager->expects($this->any())
->method('getConfiguration')
->will($this->returnValue($this->getConfiguration()));
return $manager;
}
public function getConfiguration()
{
$config = $this->getMockBuilder('Doctrine\ORM\Configuration')->getMock();
$config->expects($this->any())->method('getEntityNamespaces')->will($this->returnValue(array('Foo\\BarBundle')));
return $config;
}
}

View file

@ -0,0 +1,122 @@
<?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\Tests\Generator;
use Sensio\Bundle\GeneratorBundle\Generator\DoctrineFormGenerator;
class DoctrineFormGeneratorTest extends GeneratorTest
{
public function testGenerate()
{
$this->generateForm(false);
$this->assertTrue(file_exists($this->tmpDir.'/Form/PostType.php'));
$content = file_get_contents($this->tmpDir.'/Form/PostType.php');
$this->assertContains('namespace Foo\BarBundle\Form', $content);
$this->assertContains('class PostType extends AbstractType', $content);
$this->assertContains('->add(\'title\')', $content);
$this->assertContains('->add(\'createdAt\')', $content);
$this->assertContains('->add(\'publishedAt\')', $content);
$this->assertContains('->add(\'updatedAt\')', $content);
$this->assertContains('public function configureOptions(OptionsResolver $resolver)', $content);
$this->assertContains('\'data_class\' => \'Foo\BarBundle\Entity\Post\'', $content);
}
public function testGenerateSubNamespacedEntity()
{
$this->generateSubNamespacedEntityForm(false);
$this->assertTrue(file_exists($this->tmpDir.'/Form/Blog/PostType.php'));
$content = file_get_contents($this->tmpDir.'/Form/Blog/PostType.php');
$this->assertContains('namespace Foo\BarBundle\Form\Blog', $content);
$this->assertContains('class PostType extends AbstractType', $content);
$this->assertContains('->add(\'title\')', $content);
$this->assertContains('->add(\'createdAt\')', $content);
$this->assertContains('->add(\'publishedAt\')', $content);
$this->assertContains('->add(\'updatedAt\')', $content);
$this->assertContains('public function configureOptions(OptionsResolver $resolver)', $content);
$this->assertContains('\'data_class\' => \'Foo\BarBundle\Entity\Blog\Post\'', $content);
$this->assertContains('public function getBlockPrefix()', $content);
$this->assertContains('return \'foo_barbundle_blog_post\';', $content);
if (method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')) {
// Symfony >= 2.8
$this->assertNotContains('public function getName()', $content);
} else {
// BC Symfony 2.7
$this->assertContains('public function getName()', $content);
}
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessageRegExp: Unable to generate the PostType form class as it already exists under the .* file
*/
public function testNonOverwrittenForm()
{
$this->generateForm(false);
$this->generateForm(false);
}
public function testOverwrittenForm()
{
$this->generateForm(false);
$this->generateForm(true);
$this->assertTrue(file_exists($this->tmpDir.'/Form/PostType.php'));
}
private function generateForm($overwrite)
{
$generator = new DoctrineFormGenerator($this->filesystem);
$generator->setSkeletonDirs(__DIR__.'/../../Resources/skeleton');
$bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\BundleInterface')->getMock();
$bundle->expects($this->any())->method('getPath')->will($this->returnValue($this->tmpDir));
$bundle->expects($this->any())->method('getNamespace')->will($this->returnValue('Foo\BarBundle'));
$metadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadataInfo')->disableOriginalConstructor()->getMock();
$metadata->identifier = array('id');
$metadata->fieldMappings = array(
'title' => array('type' => 'string'),
'createdAt' => array('type' => 'date'),
'publishedAt' => array('type' => 'time'),
'updatedAt' => array('type' => 'datetime'),
);
$metadata->associationMappings = $metadata->fieldMappings;
$generator->generate($bundle, 'Post', $metadata, $overwrite);
}
private function generateSubNamespacedEntityForm($overwrite)
{
$generator = new DoctrineFormGenerator($this->filesystem);
$generator->setSkeletonDirs(__DIR__.'/../../Resources/skeleton');
$bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\BundleInterface')->getMock();
$bundle->expects($this->any())->method('getPath')->will($this->returnValue($this->tmpDir));
$bundle->expects($this->any())->method('getNamespace')->will($this->returnValue('Foo\BarBundle'));
$metadata = $this->getMockBuilder('Doctrine\ORM\Mapping\ClassMetadataInfo')->disableOriginalConstructor()->getMock();
$metadata->identifier = array('id');
$metadata->fieldMappings = array(
'title' => array('type' => 'string'),
'createdAt' => array('type' => 'date'),
'publishedAt' => array('type' => 'time'),
'updatedAt' => array('type' => 'datetime'),
);
$metadata->associationMappings = $metadata->fieldMappings;
$generator->generate($bundle, 'Blog\Post', $metadata, $overwrite);
}
}

View file

@ -0,0 +1,32 @@
<?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\Tests\Generator;
use Symfony\Component\Filesystem\Filesystem;
abstract class GeneratorTest extends \PHPUnit_Framework_TestCase
{
protected $filesystem;
protected $tmpDir;
public function setUp()
{
$this->tmpDir = sys_get_temp_dir().'/sf';
$this->filesystem = new Filesystem();
$this->filesystem->remove($this->tmpDir);
}
public function tearDown()
{
$this->filesystem->remove($this->tmpDir);
}
}

View file

@ -0,0 +1,174 @@
<?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\Tests\Generator;
use Sensio\Bundle\GeneratorBundle\Manipulator\ConfigurationManipulator;
use Sensio\Bundle\GeneratorBundle\Model\Bundle;
use Symfony\Component\Filesystem\Filesystem;
class ConfigurationManipulatorTest extends \PHPUnit_Framework_TestCase
{
protected $filesystem;
protected $tmpDir;
public function setUp()
{
$this->tmpDir = sys_get_temp_dir().'/sf';
$this->filesystem = new Filesystem();
$this->filesystem->remove($this->tmpDir);
$this->filesystem->mkdir($this->tmpDir);
}
public function tearDown()
{
$this->filesystem->remove($this->tmpDir);
}
/**
* @dataProvider getAddResourcesTests
*/
public function testAddResource($bundleName, $format, $startingContents, $expectedContents)
{
$bundle = new Bundle('Acme', $bundleName, 'src', $format, true);
$configurationPath = $this->tmpDir.'/config.yml';
file_put_contents($configurationPath, $startingContents);
$manipulator = new ConfigurationManipulator($configurationPath);
$manipulator->addResource($bundle);
$realContents = file_get_contents($configurationPath);
$this->assertEquals($expectedContents, $realContents);
}
public function getAddResourcesTests()
{
$tests = array();
// normal, .yml file
$tests[] = array(
'AppBundle',
'yml',
<<<EOF
imports:
- { resource: security.yml }
- { resource: parameters.yml }
- { resource: services.yml }
framework:
esi: { enabled: true }
translator: { fallback: en }
EOF
, <<<EOF
imports:
- { resource: security.yml }
- { resource: parameters.yml }
- { resource: services.yml }
- { resource: "@AppBundle/Resources/config/services.yml" }
framework:
esi: { enabled: true }
translator: { fallback: en }
EOF
);
// normal, xml file
$tests[] = array(
'AppBundle',
'xml',
<<<EOF
imports:
- { resource: security.yml }
- { resource: parameters.yml }
- { resource: services.yml }
framework:
esi: { enabled: true }
translator: { fallback: en }
EOF
, <<<EOF
imports:
- { resource: security.yml }
- { resource: parameters.yml }
- { resource: services.yml }
- { resource: "@AppBundle/Resources/config/services.xml" }
framework:
esi: { enabled: true }
translator: { fallback: en }
EOF
);
// imports further down
$tests[] = array(
'AppBundle',
'yml',
<<<EOF
framework:
esi: { enabled: true }
translator: { fallback: en }
imports:
- { resource: security.yml }
- { resource: parameters.yml }
- { resource: services.yml }
twig:
debug: "%kernel.debug%"
strict_variables: "%kernel.debug%"
EOF
, <<<EOF
framework:
esi: { enabled: true }
translator: { fallback: en }
imports:
- { resource: security.yml }
- { resource: parameters.yml }
- { resource: services.yml }
- { resource: "@AppBundle/Resources/config/services.yml" }
twig:
debug: "%kernel.debug%"
strict_variables: "%kernel.debug%"
EOF
);
// extra line breaks in the imports list
$tests[] = array(
'AppBundle',
'yml',
<<<EOF
imports:
- { resource: security.yml }
- { resource: parameters.yml }
- { resource: services.yml }
framework:
esi: { enabled: true }
translator: { fallback: en }
EOF
, <<<EOF
imports:
- { resource: security.yml }
- { resource: parameters.yml }
- { resource: services.yml }
- { resource: "@AppBundle/Resources/config/services.yml" }
framework:
esi: { enabled: true }
translator: { fallback: en }
EOF
);
return $tests;
}
}

View file

@ -0,0 +1,143 @@
<?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\Tests\Manipulator;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ProcessBuilder;
use Symfony\Component\Process\PhpExecutableFinder;
use Sensio\Bundle\GeneratorBundle\Tests\Generator\GeneratorTest;
use Sensio\Bundle\GeneratorBundle\Manipulator\KernelManipulator;
class KernelManipulatorTest extends GeneratorTest
{
const STUB_BUNDLE_CLASS_NAME = 'Sensio\\Bundle\\GeneratorBundle\\Tests\\Manipulator\\Stubs\\StubBundle';
const STUB_NAMESPACE = 'KernelManipulatorTest\\Stubs';
/**
* @dataProvider kernelStubFilenamesProvider
*
* @param string $kernelOriginFilePath
*/
public function testAddToArray($kernelOriginFilePath)
{
if (defined('HHVM_VERSION')) {
$this->markTestSkipped('Not supported in HHVM since it doesn\'t allow to lint PHP files.');
}
$params = $this->prepareTestKernel($kernelOriginFilePath);
list($kernelClassName, $fullpath) = $params;
$kernelClassName = self::STUB_NAMESPACE.'\\'.$kernelClassName;
$this->registerClassLoader($kernelClassName, $fullpath);
$kernel = new $kernelClassName('test', true);
$manipulator = new KernelManipulator($kernel);
$manipulator->addBundle(self::STUB_BUNDLE_CLASS_NAME);
$phpFinder = new PhpExecutableFinder();
$phpExecutable = $phpFinder->find();
$this->assertNotFalse($phpExecutable, 'Php executable binary found');
$arguments = array($phpExecutable, '-l', $fullpath);
// ProcessBuilder is deprecated in symfony/process version 3.4,
// but Process doesn't accept an array of arguments in versions <3.3,
// in which ProcessUtils::escapeArgument() is not marked as deprecated
$useProcess = true;
if (method_exists('Symfony\Component\Process\ProcessUtils', 'escapeArgument')) {
$r = new \ReflectionMethod('Symfony\Component\Process\ProcessUtils', 'escapeArgument');
if ($r->isPublic() && false === strpos($r->getDocComment(), '@deprecated')) {
$useProcess = false;
}
}
if ($useProcess) {
$process = new Process($arguments);
// preserve the BC with symfony <3.3
$process->setCommandLine($process->getCommandLine());
} else {
$pb = new ProcessBuilder($arguments);
$process = $pb->getProcess();
}
$process->run();
$result = strpos($process->getOutput(), 'No syntax errors detected');
$this->assertNotFalse($result, 'Manipulator should not provoke syntax errors');
}
/**
* @return array
*/
public function kernelStubFilenamesProvider()
{
$stubs = array(
'With empty bundles array' => array(__DIR__.'/Stubs/EmptyBundlesKernelStub.php'),
'With empty multiline bundles array' => array(__DIR__.'/Stubs/EmptyBundlesMultilineKernelStub.php'),
'With bundles array contains comma' => array(__DIR__.'/Stubs/ContainsCommaKernelStub.php'),
'With bundles added w/o trailing comma' => array(__DIR__.'/Stubs/ContainsBundlesKernelStub.php'),
'With some extra code and bad formatted' => array(__DIR__.'/Stubs/ContainsExtraCodeKernelStub.php'),
);
if (PHP_VERSION_ID >= 50400) {
$stubs = array_merge($stubs, array(
'With empty bundles array, short array syntax' => array(__DIR__.'/Stubs/EmptyBundlesShortArraySyntaxKernelStub.php'),
'With empty multiline bundles array, short array syntax' => array(__DIR__.'/Stubs/EmptyBundlesMultilineShortArraySyntaxKernelStub.php'),
'With bundles array contains comma, short array syntax' => array(__DIR__.'/Stubs/ContainsCommaShortArraySyntaxKernelStub.php'),
'With bundles added w/o trailing comma, short array syntax' => array(__DIR__.'/Stubs/ContainsBundlesShortArraySyntaxKernelStub.php'),
));
}
return $stubs;
}
/**
* Copies stub file to tmp.
*
* @param string $kernelOriginFilePath
*
* @return array
*/
protected function prepareTestKernel($kernelOriginFilePath)
{
$pathInfo = pathinfo($kernelOriginFilePath);
$fileName = $pathInfo['basename'];
$className = $pathInfo['filename'];
$targetDir = $this->tmpDir.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, self::STUB_NAMESPACE);
$this->filesystem->mkdir($targetDir);
$targetPath = $targetDir.DIRECTORY_SEPARATOR.$fileName;
$this->filesystem->copy($kernelOriginFilePath, $targetPath, true);
return array($className, $targetPath);
}
/**
* Registers the stubs namespace in the autoloader.
*
* @param string $kernelClassName
* @param string $fullpath
*/
protected function registerClassLoader($kernelClassName, $fullpath)
{
spl_autoload_register(
function ($class) use ($kernelClassName, $fullpath) {
if ($class === $kernelClassName) {
require $fullpath;
return true;
}
}
);
}
}

View file

@ -0,0 +1,96 @@
<?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\Tests\Manipulator;
use Sensio\Bundle\GeneratorBundle\Manipulator\RoutingManipulator;
class RoutingManipulatorTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getImportedResourceYamlKeys
*/
public function testGetImportedResourceYamlKey($bundleName, $prefix, $expectedKey)
{
$manipulator = new RoutingManipulator(__FILE__);
$key = $manipulator->getImportedResourceYamlKey($bundleName, $prefix);
$this->assertEquals($expectedKey, $key);
}
public function getImportedResourceYamlKeys()
{
return array(
array('AppBundle', '', 'app'),
array('AppBundle', '/', 'app'),
array('AppBundle', '//', 'app'),
array('AppBundle', '/{foo}', 'app'),
array('AppBundle', '/{_foo}', 'app'),
array('AppBundle', '/{/foo}', 'app'),
array('AppBundle', '/{/foo/}', 'app'),
array('AppBundle', '/{_locale}', 'app'),
array('AppBundle', '/{_locale}/foo', 'app_foo'),
array('AppBundle', '/{_locale}/foo/', 'app_foo'),
array('AppBundle', '/{_locale}/foo/{_format}', 'app_foo'),
array('AppBundle', '/{_locale}/foo/{_format}/', 'app_foo'),
array('AppBundle', '/{_locale}/foo/{_format}/bar', 'app_foo_bar'),
array('AppBundle', '/{_locale}/foo/{_format}/bar/', 'app_foo_bar'),
array('AppBundle', '/{_locale}/foo/{_format}/bar//', 'app_foo_bar'),
array('AcmeBlogBundle', '', 'acme_blog'),
array('AcmeBlogBundle', '/', 'acme_blog'),
array('AcmeBlogBundle', '//', 'acme_blog'),
array('AcmeBlogBundle', '/{_locale}', 'acme_blog'),
array('AcmeBlogBundle', '/{_locale}/foo', 'acme_blog_foo'),
array('AcmeBlogBundle', '/{_locale}/foo/', 'acme_blog_foo'),
array('AcmeBlogBundle', '/{_locale}/foo/{_format}', 'acme_blog_foo'),
array('AcmeBlogBundle', '/{_locale}/foo/{_format}/', 'acme_blog_foo'),
array('AcmeBlogBundle', '/{_locale}/foo/{_format}/bar', 'acme_blog_foo_bar'),
array('AcmeBlogBundle', '/{_locale}/foo/{_format}/bar/', 'acme_blog_foo_bar'),
array('AcmeBlogBundle', '/{_locale}/foo/{_format}/bar//', 'acme_blog_foo_bar'),
);
}
public function testHasResourceInAnnotation()
{
$tmpDir = sys_get_temp_dir().'/sf';
@mkdir($tmpDir, 0777, true);
$file = tempnam($tmpDir, 'routing');
$routing = <<<DATA
acme_demo:
resource: "@AcmeDemoBundle/Controller/"
type: annotation
DATA;
file_put_contents($file, $routing);
$manipulator = new RoutingManipulator($file);
$this->assertTrue($manipulator->hasResourceInAnnotation('AcmeDemoBundle'));
}
public function testHasResourceInAnnotationReturnFalseIfOnlyOneControllerDefined()
{
$tmpDir = sys_get_temp_dir().'/sf';
@mkdir($tmpDir, 0777, true);
$file = tempnam($tmpDir, 'routing');
$routing = <<<DATA
acme_demo_post:
resource: "@AcmeDemoBundle/Controller/PostController.php"
type: annotation
DATA;
file_put_contents($file, $routing);
$manipulator = new RoutingManipulator($file);
$this->assertFalse($manipulator->hasResourceInAnnotation('AcmeDemoBundle'));
}
}

View file

@ -0,0 +1,29 @@
<?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 KernelManipulatorTest\Stubs;
use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest;
use Sensio\Bundle\GeneratorBundle\Tests\Manipulator\Stubs\StubBundle;
class ContainsBundlesKernelStub extends KernelForTest
{
public function registerBundles()
{
$bundles = array(
new StubBundle(),
new StubBundle(),
new StubBundle(),
);
return $bundles;
}
}

View file

@ -0,0 +1,29 @@
<?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 KernelManipulatorTest\Stubs;
use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest;
use Sensio\Bundle\GeneratorBundle\Tests\Manipulator\Stubs\StubBundle;
class ContainsBundlesShortArraySyntaxKernelStub extends KernelForTest
{
public function registerBundles()
{
$bundles = [
new StubBundle(),
new StubBundle(),
new StubBundle(),
];
return $bundles;
}
}

View file

@ -0,0 +1,27 @@
<?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 KernelManipulatorTest\Stubs;
use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest;
use Sensio\Bundle\GeneratorBundle\Tests\Manipulator\Stubs\StubBundle;
class ContainsCommaKernelStub extends KernelForTest
{
public function registerBundles()
{
$bundles = array(
new StubBundle(),
);
return $bundles;
}
}

View file

@ -0,0 +1,27 @@
<?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 KernelManipulatorTest\Stubs;
use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest;
use Sensio\Bundle\GeneratorBundle\Tests\Manipulator\Stubs\StubBundle;
class ContainsCommaShortArraySyntaxKernelStub extends KernelForTest
{
public function registerBundles()
{
$bundles = [
new StubBundle(),
];
return $bundles;
}
}

View file

@ -0,0 +1,37 @@
<?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 KernelManipulatorTest\Stubs;
use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest;
use Sensio\Bundle\GeneratorBundle\Tests\Manipulator\Stubs\StubBundle;
class ContainsExtraCodeKernelStub extends KernelForTest
{
public function registerBundles()
{
$someVariable = false;
// some bad formatted definition
$bundles = array(
new StubBundle(),
new StubBundle(),
);
if (!$someVariable && in_array($this->getEnvironment(), array('dev', 'test'))) {
$bundles[] = new StubBundle();
}
return $bundles;
}
}

View file

@ -0,0 +1,37 @@
<?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 KernelManipulatorTest\Stubs;
use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest;
use Sensio\Bundle\GeneratorBundle\Tests\Manipulator\Stubs\StubBundle;
class ContainsExtraCodeShortArraySyntaxKernelStub extends KernelForTest
{
public function registerBundles()
{
$someVariable = false;
// some bad formatted definition
$bundles = [
new StubBundle(),
new StubBundle(),
];
if (!$someVariable && in_array($this->getEnvironment(), array('dev', 'test'))) {
$bundles[] = new StubBundle();
}
return $bundles;
}
}

View file

@ -0,0 +1,24 @@
<?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 KernelManipulatorTest\Stubs;
use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest;
class EmptyBundlesKernelStub extends KernelForTest
{
public function registerBundles()
{
$bundles = array();
return $bundles;
}
}

View file

@ -0,0 +1,26 @@
<?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 KernelManipulatorTest\Stubs;
use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest;
class EmptyBundlesMultilineKernelStub extends KernelForTest
{
public function registerBundles()
{
$bundles = array(
);
return $bundles;
}
}

View file

@ -0,0 +1,26 @@
<?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 KernelManipulatorTest\Stubs;
use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest;
class EmptyBundlesMultilineShortArraySyntaxKernelStub extends KernelForTest
{
public function registerBundles()
{
$bundles = [
];
return $bundles;
}
}

View file

@ -0,0 +1,24 @@
<?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 KernelManipulatorTest\Stubs;
use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest;
class EmptyBundlesShortArraySyntaxKernelStub extends KernelForTest
{
public function registerBundles()
{
$bundles = [];
return $bundles;
}
}

View file

@ -0,0 +1,18 @@
<?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\Tests\Manipulator\Stubs;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class StubBundle extends Bundle
{
}

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 KernelManipulatorTest\Stubs;
use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest;
use Sensio\Bundle\GeneratorBundle\Tests\Manipulator\Stubs\StubBundle;
class UsingShortArraySyntaxKernelStub extends KernelForTest
{
public function registerBundles()
{
// short array syntax
$bundles = [
new StubBundle(),
new StubBundle(),
];
if (!$someVariable && in_array($this->getEnvironment(), array('dev', 'test'))) {
$bundles[] = new StubBundle();
}
return $bundles;
}
}

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