collection-crud/src/Maker/MakeCollectionCrud.php

325 lines
11 KiB
PHP

<?php
/*
* This file is part of the Symfony MakerBundle 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 App\Maker;
use App\Traits\FormControllerTrait;
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Bundle\MakerBundle\ConsoleStyle;
use Symfony\Bundle\MakerBundle\DependencyBuilder;
use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper;
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Bundle\MakerBundle\InputConfiguration;
use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Bundle\MakerBundle\Renderer\FormTypeRenderer;
use Symfony\Bundle\MakerBundle\Str;
use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator;
use Symfony\Bundle\MakerBundle\Validator;
use Symfony\Bundle\TwigBundle\TwigBundle;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Symfony\Component\Validator\Validation;
/**
* @author Sadicov Vladimir <sadikoff@gmail.com>
*/
final class MakeCollectionCrud extends AbstractMaker
{
private Inflector $inflector;
private string $controllerClassName;
private bool $generateTests = false;
public function __construct(private DoctrineHelper $doctrineHelper, private FormTypeRenderer $formTypeRenderer)
{
$this->inflector = InflectorFactory::create()->build();
define('CRUD_TEMPLATE_PREFIX', __DIR__.'/../Resources/crud');
}
public static function getCommandName(): string
{
return 'make:collection-crud';
}
public static function getCommandDescription(): string
{
return 'Creates custom CRUD for this App';
}
public function configureCommand(Command $command, InputConfiguration $inputConfig): void
{
$command
->addArgument('entity-class', InputArgument::OPTIONAL, sprintf('The class name of the entity to create CRUD (e.g. <fg=yellow>%s</>)', Str::asClassName(Str::getRandomTerm())))
->setHelp(file_get_contents(__DIR__.'/../Resources/help/MakeCrud.txt'))
;
$inputConfig->setArgumentAsNonInteractive('entity-class');
}
public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void
{
if (null === $input->getArgument('entity-class')) {
$argument = $command->getDefinition()->getArgument('entity-class');
$entities = $this->doctrineHelper->getEntitiesForAutocomplete();
$question = new Question($argument->getDescription());
$question->setAutocompleterValues($entities);
$value = $io->askQuestion($question);
$input->setArgument('entity-class', $value);
}
$defaultControllerClass = Str::asClassName(sprintf('%s Controller', $input->getArgument('entity-class')));
$this->controllerClassName = $io->ask(
sprintf('Choose a name for your controller class (e.g. <fg=yellow>%s</>)', $defaultControllerClass),
$defaultControllerClass
);
$this->generateTests = $io->confirm('Do you want to generate tests for the controller?. [Experimental]', false);
}
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void
{
$entityClassDetails = $generator->createClassNameDetails(
Validator::entityExists($input->getArgument('entity-class'), $this->doctrineHelper->getEntitiesForAutocomplete()),
'Entity\\'
);
$entityDoctrineDetails = $this->doctrineHelper->createDoctrineDetails($entityClassDetails->getFullName());
$repositoryVars = [];
$repositoryClassName = EntityManagerInterface::class;
if (null !== $entityDoctrineDetails->getRepositoryClass()) {
$repositoryClassDetails = $generator->createClassNameDetails(
'\\'.$entityDoctrineDetails->getRepositoryClass(),
'Repository\\',
'Repository'
);
$repositoryClassName = $repositoryClassDetails->getFullName();
$repositoryVars = [
'repository_full_class_name' => $repositoryClassName,
'repository_class_name' => $repositoryClassDetails->getShortName(),
'repository_var' => lcfirst($this->inflector->singularize($repositoryClassDetails->getShortName())),
];
}
$controllerClassDetails = $generator->createClassNameDetails(
$this->controllerClassName,
'Controller\\',
'Controller'
);
$iter = 0;
do {
$formClassDetails = $generator->createClassNameDetails(
$entityClassDetails->getRelativeNameWithoutSuffix().($iter ?: '').'Type',
'Form\\',
'Type'
);
++$iter;
} while (class_exists($formClassDetails->getFullName()));
$entityVarPlural = lcfirst($this->inflector->pluralize($entityClassDetails->getShortName()));
$entityVarSingular = lcfirst($this->inflector->singularize($entityClassDetails->getShortName()));
$entityTwigVarPlural = Str::asTwigVariable($entityVarPlural);
$entityTwigVarSingular = Str::asTwigVariable($entityVarSingular);
$routeName = Str::asRouteName($controllerClassDetails->getRelativeNameWithoutSuffix());
$templatesPath = Str::asFilePath($controllerClassDetails->getRelativeNameWithoutSuffix());
$useStatements = new UseStatementGenerator([
$entityClassDetails->getFullName(),
$formClassDetails->getFullName(),
$repositoryClassName,
AbstractController::class,
Request::class,
Response::class,
Route::class,
FormControllerTrait::class,
]);
$generator->generateController(
$controllerClassDetails->getFullName(),
CRUD_TEMPLATE_PREFIX . '/controller/Controller.tpl.php',
array_merge([
'use_statements' => $useStatements,
'entity_class_name' => $entityClassDetails->getShortName(),
'form_class_name' => $formClassDetails->getShortName(),
'route_path' => Str::asRoutePath($controllerClassDetails->getRelativeNameWithoutSuffix()),
'route_name' => $routeName,
'templates_path' => $templatesPath,
'entity_var_plural' => $entityVarPlural,
'entity_twig_var_plural' => $entityTwigVarPlural,
'entity_var_singular' => $entityVarSingular,
'entity_twig_var_singular' => $entityTwigVarSingular,
'entity_identifier' => $entityDoctrineDetails->getIdentifier(),
'use_render_form' => method_exists(AbstractController::class, 'renderForm'),
],
$repositoryVars
)
);
$this->formTypeRenderer->render(
$formClassDetails,
$entityDoctrineDetails->getFormFields(),
$entityClassDetails
);
$templates = [
'edit' => [
'entity_class_name' => $entityClassDetails->getShortName(),
'entity_twig_var_singular' => $entityTwigVarSingular,
'entity_identifier' => $entityDoctrineDetails->getIdentifier(),
'route_name' => $routeName,
'templates_path' => $templatesPath,
],
'index' => [
'entity_class_name' => $entityClassDetails->getShortName(),
'entity_twig_var_plural' => $entityTwigVarPlural,
'entity_twig_var_singular' => $entityTwigVarSingular,
'entity_identifier' => $entityDoctrineDetails->getIdentifier(),
'entity_fields' => $entityDoctrineDetails->getDisplayFields(),
'route_name' => $routeName,
],
'new' => [
'entity_class_name' => $entityClassDetails->getShortName(),
'route_name' => $routeName,
'templates_path' => $templatesPath,
],
'show' => [
'entity_class_name' => $entityClassDetails->getShortName(),
'entity_twig_var_singular' => $entityTwigVarSingular,
'entity_identifier' => $entityDoctrineDetails->getIdentifier(),
'entity_fields' => $entityDoctrineDetails->getDisplayFields(),
'route_name' => $routeName,
'templates_path' => $templatesPath,
],
];
foreach ($templates as $template => $variables) {
$generator->generateTemplate(
$templatesPath.'/'.$template.'.html.twig',
CRUD_TEMPLATE_PREFIX . '/templates/'.$template.'.tpl.php',
$variables
);
}
if ($this->generateTests) {
$testClassDetails = $generator->createClassNameDetails(
$entityClassDetails->getRelativeNameWithoutSuffix(),
'Test\\Controller\\',
'ControllerTest'
);
$useStatements = new UseStatementGenerator([
$entityClassDetails->getFullName(),
WebTestCase::class,
KernelBrowser::class,
$repositoryClassName,
]);
$usesEntityManager = EntityManagerInterface::class === $repositoryClassName;
if ($usesEntityManager) {
$useStatements->addUseStatement(EntityRepository::class);
}
$generator->generateFile(
'tests/Controller/'.$testClassDetails->getShortName().'.php',
$usesEntityManager ? 'crud/test/Test.EntityManager.tpl.php' : 'crud/test/Test.tpl.php',
[
'use_statements' => $useStatements,
'entity_full_class_name' => $entityClassDetails->getFullName(),
'entity_class_name' => $entityClassDetails->getShortName(),
'entity_var_singular' => $entityVarSingular,
'route_path' => Str::asRoutePath($controllerClassDetails->getRelativeNameWithoutSuffix()),
'route_name' => $routeName,
'class_name' => Str::getShortClassName($testClassDetails->getFullName()),
'namespace' => Str::getNamespace($testClassDetails->getFullName()),
'form_fields' => $entityDoctrineDetails->getFormFields(),
'repository_class_name' => $usesEntityManager ? EntityManagerInterface::class : $repositoryVars['repository_class_name'],
'form_field_prefix' => strtolower(Str::asSnakeCase($entityTwigVarSingular)),
]
);
if (!class_exists(WebTestCase::class)) {
$io->caution('You\'ll need to install the `symfony/test-pack` to execute the tests for your new controller.');
}
}
$generator->writeChanges();
$this->writeSuccessMessage($io);
$io->text(sprintf('Next: Check your new CRUD by going to <fg=yellow>%s/</>', Str::asRoutePath($controllerClassDetails->getRelativeNameWithoutSuffix())));
}
public function configureDependencies(DependencyBuilder $dependencies): void
{
$dependencies->addClassDependency(
Route::class,
'router'
);
$dependencies->addClassDependency(
AbstractType::class,
'form'
);
$dependencies->addClassDependency(
Validation::class,
'validator'
);
$dependencies->addClassDependency(
TwigBundle::class,
'twig-bundle'
);
$dependencies->addClassDependency(
DoctrineBundle::class,
'orm'
);
$dependencies->addClassDependency(
CsrfTokenManager::class,
'security-csrf'
);
$dependencies->addClassDependency(
ParamConverter::class,
'annotations'
);
}
}