Update
This commit is contained in:
parent
a37785b391
commit
33458b2ca3
9915 changed files with 1247019 additions and 0 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
210
trunk/_vendor/sensio/generator-bundle/Command/Validators.php
Normal file
210
trunk/_vendor/sensio/generator-bundle/Command/Validators.php
Normal 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',
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue