Add custom maker to generate CRUD consistent with existing sections, so there is less work needed after generating the CRUD for the specific Entity

This commit is contained in:
Timothy Warren 2022-10-27 11:53:34 -04:00
parent 195101ffde
commit 27f5cd792f
13 changed files with 799 additions and 14 deletions

View File

@ -11,6 +11,7 @@
"symfony/form": "^6.0.3",
"symfony/maker-bundle": "^1.0",
"symfony/monolog-bundle": "^3.0",
"symfony/security-bundle": "^6.1",
"symfony/security-csrf": "^6.1",
"symfony/translation": "^6.0.3",
"symfony/twig-bundle": "^6.0",

195
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "9ca36f3fba137c0f63b47e4f71a2ac9f",
"content-hash": "191395e56b0c475bdbd184fe246918f8",
"packages": [
{
"name": "composer/package-versions-deprecated",
@ -4677,6 +4677,106 @@
],
"time": "2022-09-09T09:26:14+00:00"
},
{
"name": "symfony/security-bundle",
"version": "v6.1.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/security-bundle.git",
"reference": "1410129e36e5d0cf4bde73f4ed5d9e18acff06b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/security-bundle/zipball/1410129e36e5d0cf4bde73f4ed5d9e18acff06b3",
"reference": "1410129e36e5d0cf4bde73f4ed5d9e18acff06b3",
"shasum": ""
},
"require": {
"composer-runtime-api": ">=2.1",
"ext-xml": "*",
"php": ">=8.1",
"symfony/config": "^5.4|^6.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/event-dispatcher": "^5.4|^6.0",
"symfony/http-foundation": "^5.4|^6.0",
"symfony/http-kernel": "^5.4|^6.0",
"symfony/password-hasher": "^5.4|^6.0",
"symfony/security-core": "^5.4|^6.0",
"symfony/security-csrf": "^5.4|^6.0",
"symfony/security-http": "^5.4|^6.0"
},
"conflict": {
"symfony/browser-kit": "<5.4",
"symfony/console": "<5.4",
"symfony/framework-bundle": "<5.4",
"symfony/ldap": "<5.4",
"symfony/twig-bundle": "<5.4"
},
"require-dev": {
"doctrine/annotations": "^1.10.4",
"symfony/asset": "^5.4|^6.0",
"symfony/browser-kit": "^5.4|^6.0",
"symfony/console": "^5.4|^6.0",
"symfony/css-selector": "^5.4|^6.0",
"symfony/dom-crawler": "^5.4|^6.0",
"symfony/expression-language": "^5.4|^6.0",
"symfony/form": "^5.4|^6.0",
"symfony/framework-bundle": "^5.4|^6.0",
"symfony/ldap": "^5.4|^6.0",
"symfony/process": "^5.4|^6.0",
"symfony/rate-limiter": "^5.4|^6.0",
"symfony/serializer": "^5.4|^6.0",
"symfony/translation": "^5.4|^6.0",
"symfony/twig-bridge": "^5.4|^6.0",
"symfony/twig-bundle": "^5.4|^6.0",
"symfony/validator": "^5.4|^6.0",
"symfony/yaml": "^5.4|^6.0",
"twig/twig": "^2.13|^3.0.4"
},
"type": "symfony-bundle",
"autoload": {
"psr-4": {
"Symfony\\Bundle\\SecurityBundle\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides a tight integration of the Security component into the Symfony full-stack framework",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/security-bundle/tree/v6.1.3"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-07-20T13:46:29+00:00"
},
{
"name": "symfony/security-core",
"version": "v6.1.6",
@ -4839,6 +4939,89 @@
],
"time": "2022-05-14T12:53:54+00:00"
},
{
"name": "symfony/security-http",
"version": "v6.1.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/security-http.git",
"reference": "789492510f7127035da8bb5dbb4fb745970e2348"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/security-http/zipball/789492510f7127035da8bb5dbb4fb745970e2348",
"reference": "789492510f7127035da8bb5dbb4fb745970e2348",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/http-foundation": "^5.4|^6.0",
"symfony/http-kernel": "^6.1",
"symfony/polyfill-mbstring": "~1.0",
"symfony/property-access": "^5.4|^6.0",
"symfony/security-core": "^5.4.7|^6.0"
},
"conflict": {
"symfony/event-dispatcher": "<5.4.9|>=6,<6.0.9",
"symfony/security-bundle": "<5.4",
"symfony/security-csrf": "<5.4"
},
"require-dev": {
"psr/log": "^1|^2|^3",
"symfony/cache": "^5.4|^6.0",
"symfony/rate-limiter": "^5.4|^6.0",
"symfony/routing": "^5.4|^6.0",
"symfony/security-csrf": "^5.4|^6.0",
"symfony/translation": "^5.4|^6.0"
},
"suggest": {
"symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs",
"symfony/security-csrf": "For using tokens to protect authentication/logout attempts"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Security\\Http\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Security Component - HTTP Integration",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/security-http/tree/v6.1.6"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-10-01T16:55:12+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v3.1.1",
@ -6472,12 +6655,12 @@
"source": {
"type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git",
"reference": "599b9d6746e56b67b187afed175dd02fb1a288fa"
"reference": "d55c618dc2c5141caa00a416ca21bb6f06cf00e0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/599b9d6746e56b67b187afed175dd02fb1a288fa",
"reference": "599b9d6746e56b67b187afed175dd02fb1a288fa",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/d55c618dc2c5141caa00a416ca21bb6f06cf00e0",
"reference": "d55c618dc2c5141caa00a416ca21bb6f06cf00e0",
"shasum": ""
},
"conflict": {
@ -6500,6 +6683,7 @@
"asymmetricrypt/asymmetricrypt": ">=0,<9.9.99",
"awesome-support/awesome-support": "<=6.0.7",
"aws/aws-sdk-php": ">=3,<3.2.1",
"badaso/core": "<2.6.1",
"bagisto/bagisto": "<0.1.5",
"barrelstrength/sprout-base-email": "<1.2.7",
"barrelstrength/sprout-forms": "<3.9",
@ -6655,6 +6839,7 @@
"joomla/filter": "<1.4.4|>=2,<2.0.1",
"joomla/input": ">=2,<2.0.2",
"joomla/session": "<1.3.1",
"joyqi/hyper-down": "<=2.4.27",
"jsdecena/laracom": "<2.0.9",
"jsmitty12/phpwhois": "<5.1",
"kazist/phpwhois": "<=4.2.6",
@ -6993,7 +7178,7 @@
"type": "tidelift"
}
],
"time": "2022-10-24T19:11:20+00:00"
"time": "2022-10-27T00:18:37+00:00"
},
{
"name": "sebastian/cli-parser",

View File

@ -1,12 +1,13 @@
<?php declare(strict_types=1);
<?php
return [
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => TRUE],
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => TRUE],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => TRUE],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => TRUE],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => TRUE, 'test' => TRUE],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => TRUE],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => TRUE],
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => TRUE],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
];

View File

@ -0,0 +1,39 @@
security:
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
users_in_memory: { memory: null }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: users_in_memory
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
when@test:
security:
password_hashers:
# By default, password hashers are resource intensive and take time. This is
# important to generate secure password hashes. In tests however, secure hashes
# are not important, waste resources and increase test times. The following
# reduces the work factor to the lowest possible values.
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon

View File

@ -32,3 +32,8 @@ services:
# App\Service\ExampleService:
# arguments:
# $someArgument: 'some_value'
App\Maker\MakeCollectionCrud:
arguments:
'$doctrineHelper': '@maker.doctrine_helper'
'$formTypeRenderer': '@maker.renderer.form_type_renderer'

View File

@ -0,0 +1,324 @@
<?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'
);
}
}

View File

@ -0,0 +1,69 @@
<?= "<?php declare(strict_types=1);\n" ?>
namespace <?= $namespace ?>;
<?= $use_statements; ?>
#[Route('<?= $route_path ?>')]
class <?= $class_name ?> extends AbstractController {
use FormControllerTrait;
protected const ENTITY = <?= $entity_class_name ?>::class;
protected const TEMPLATE_PATH = '<?= $entity_twig_var_singular ?>/';
protected const ROUTE_PREFIX = '<?= $entity_twig_var_singular ?>_';
protected const FORM = <?= $form_class_name ?>::class;
public function __construct(private readonly EntityManagerInterface $entityManager)
{
}
<?= $generator->generateRouteForControllerMethod('/', sprintf('%s_index', $route_name), ['GET']) ?>
<?php if (isset($repository_full_class_name)): ?>
public function index(<?= $repository_class_name ?> $<?= $repository_var ?>): Response
{
return $this->render('<?= $templates_path ?>/index.html.twig', [
'<?= $entity_twig_var_plural ?>' => $<?= $repository_var ?>->findAll(),
]);
}
<?php else: ?>
public function index(): Response
{
return $this->itemListView('<?= $entity_twig_var_plural ?>', []);
}
<?php endif ?>
<?= $generator->generateRouteForControllerMethod('/new', sprintf('%s_new', $route_name), ['GET', 'POST']) ?>
<?php if (isset($repository_full_class_name) && $generator->repositoryHasSaveAndRemoveMethods($repository_full_class_name)) { ?>
public function new(Request $request, <?= $repository_class_name ?> $<?= $repository_var ?>): Response
<?php } else { ?>
public function new(Request $request): Response
<?php } ?>
{
return $this->itemCreate($request, '<?= $entity_twig_var_singular ?>');
}
<?= $generator->generateRouteForControllerMethod(sprintf('/{%s}', $entity_identifier), sprintf('%s_show', $route_name), ['GET']) ?>
public function show(<?= $entity_class_name ?> $<?= $entity_var_singular ?>): Response
{
return $this->itemView($<?= $entity_var_singular ?>, '<?= $entity_twig_var_singular ?>');
}
<?= $generator->generateRouteForControllerMethod(sprintf('/{%s}/edit', $entity_identifier), sprintf('%s_edit', $route_name), ['GET', 'POST']) ?>
<?php if (isset($repository_full_class_name) && $generator->repositoryHasSaveAndRemoveMethods($repository_full_class_name)) { ?>
public function edit(Request $request, <?= $entity_class_name ?> $<?= $entity_var_singular ?>, <?= $repository_class_name ?> $<?= $repository_var ?>): Response
<?php } else { ?>
public function edit(Request $request, <?= $entity_class_name ?> $<?= $entity_var_singular ?>): Response
<?php } ?>
{
return $this->itemUpdate($request, $<?= $entity_var_singular ?>, '<?= $entity_twig_var_singular ?>');
}
<?= $generator->generateRouteForControllerMethod(sprintf('/{%s}', $entity_identifier), sprintf('%s_delete', $route_name), ['POST']) ?>
<?php if (isset($repository_full_class_name) && $generator->repositoryHasSaveAndRemoveMethods($repository_full_class_name)) { ?>
public function delete(Request $request, <?= $entity_class_name ?> $<?= $entity_var_singular ?>, <?= $repository_class_name ?> $<?= $repository_var ?>): Response
<?php } else { ?>
public function delete(Request $request, <?= $entity_class_name ?> $<?= $entity_var_singular ?>): Response
<?php } ?>
{
return $this->deleteCSRF($request, $<?= $entity_var_singular ?>);
}
}

View File

@ -0,0 +1,30 @@
{% extends 'form.html.twig' %}
{% block title %}<?= $entity_class_name ?> - Edit{% endblock %}
{% block form %}
<h2>Edit <?= $entity_class_name ?></h2>
<div class="small callout">
<ul>
<li>
<a href="{{ path('<?= $route_name ?>_index') }}">Back to the list</a>
</li>
</ul>
</div>
<div class="large primary callout">
{{ form_start(edit_form) }}
{{ form_widget(edit_form) }}
<button
type="submit"
class="success button expanded"
>Update</button>
{{ form_end(edit_form) }}
<form method="post" action="{{ path('<?= $route_name ?>_delete', {'<?= $entity_identifier ?>': <?= $entity_twig_var_singular ?>.<?= $entity_identifier ?>}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ <?= $entity_twig_var_singular ?>.<?= $entity_identifier ?>) }}">
<button type="submit" class="alert button expanded">Delete</button>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,51 @@
<?= $helper->getHeadPrintCode($entity_class_name); ?>
{% block body %}
<h2><?= $entity_class_name ?></h2>
<div class="small callout primary">
<ul>
<li>
<a href="{{ path('<?= $route_name ?>_new') }}">Add <?= $entity_class_name ?></a>
</li>
</ul>
</div>
<table class="table">
<thead>
<tr>
<th>&nbsp;</th>
<?php foreach ($entity_fields as $field): ?>
<th><?= ucfirst($field['fieldName']) ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
{% for <?= $entity_twig_var_singular ?> in <?= $entity_twig_var_plural ?> %}
<tr>
<td>
<ul>
<li>
<a href="{{ path('<?= $route_name ?>_show', {'<?= $entity_identifier ?>': <?= $entity_twig_var_singular ?>.<?= $entity_identifier ?>}) }}">
View 👁
</a>
</li>
<li>
<a href="{{ path('<?= $route_name ?>_edit', {'<?= $entity_identifier ?>': <?= $entity_twig_var_singular ?>.<?= $entity_identifier ?>}) }}">
Edit <span class="edit-icon">&#9998;</span>
</a>
</li>
</ul>
</td>
<?php foreach ($entity_fields as $field): ?>
<td>{{ <?= $helper->getEntityFieldPrintCode($entity_twig_var_singular, $field) ?> }}</td>
<?php endforeach; ?>
</tr>
{% else %}
<tr>
<td colspan="<?= (count($entity_fields) + 1) ?>">no records found</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends 'form.html.twig' %}
{% block title %}<?= $entity_class_name ?> - New{% endblock %}
{% block form %}
<h2>Add <?= $entity_class_name ?></h2>
<div class="small callout">
<ul>
<li>
<a href="{{ path('<?= $route_name ?>_index') }}">Back to the list</a>
</li>
</ul>
</div>
<div class="large primary callout">
{{ form_start(form) }}
{{ form_widget(form) }}
<button type="submit" class="success button expanded">Add</button>
{{ form_end(form) }}
</div>
{% endblock %}

View File

@ -0,0 +1,40 @@
{% extends 'form.html.twig' %}
{% block title %}<?= $entity_class_name ?>{% endblock %}
{% block form %}
<h2>Brand</h2>
<div class="callout">
<ul>
<li>
<a href="{{ path('<?= $route_name ?>_index') }}">Back to the list</a>
</li>
<li>
<a href="{{ path('<?= $route_name ?>_edit', {'<?= $entity_identifier ?>': <?= $entity_twig_var_singular ?>.<?= $entity_identifier ?>}) }}">Edit</a>
</li>
</ul>
<hr/>
<form method="post" action="{{ path('<?= $route_name ?>_delete', {'<?= $entity_identifier ?>': <?= $entity_twig_var_singular ?>.<?= $entity_identifier ?>}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ <?= $entity_twig_var_singular ?>.<?= $entity_identifier ?>) }}">
<button type="submit" class="alert button expanded">Delete</button>
</form>
</div>
<div class="large primary callout">
<table class="table">
<tbody>
<?php foreach ($entity_fields as $field): ?>
<tr>
<th><?= ucfirst($field['fieldName']) ?></th>
<td>{{ <?= $helper->getEntityFieldPrintCode($entity_twig_var_singular, $field) ?> }}</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
{% endblock %}

View File

@ -0,0 +1,5 @@
The <info>%command.name%</info> command generates crud controller with templates for selected entity.
<info>php %command.full_name% BlogPost</info>
If the argument is missing, the command will ask for the entity class name interactively.

View File

@ -381,6 +381,18 @@
"config/routes.yaml"
]
},
"symfony/security-bundle": {
"version": "6.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.0",
"ref": "8a5b112826f7d3d5b07027f93786ae11a1c7de48"
},
"files": [
"config/packages/security.yaml"
]
},
"symfony/service-contracts": {
"version": "v2.2.0"
},