358 lines
14 KiB
PHP
358 lines
14 KiB
PHP
<?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);
|
|
}
|
|
}
|