diff --git a/composer.json b/composer.json index efc79d1..aa1369f 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/composer.lock b/composer.lock index c39cc3b..2f27bd7 100644 --- a/composer.lock +++ b/composer.lock @@ -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", diff --git a/config/bundles.php b/config/bundles.php index 58337aa..5580e7c 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -1,12 +1,13 @@ - ['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], ]; diff --git a/config/packages/security.yaml b/config/packages/security.yaml new file mode 100644 index 0000000..367af25 --- /dev/null +++ b/config/packages/security.yaml @@ -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 diff --git a/config/services.yaml b/config/services.yaml index 98c72d7..4d54fec 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -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' diff --git a/src/Maker/MakeCollectionCrud.php b/src/Maker/MakeCollectionCrud.php new file mode 100644 index 0000000..3673162 --- /dev/null +++ b/src/Maker/MakeCollectionCrud.php @@ -0,0 +1,324 @@ + + * + * 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 + */ +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. %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. %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 %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' + ); + } +} diff --git a/src/Resources/crud/controller/Controller.tpl.php b/src/Resources/crud/controller/Controller.tpl.php new file mode 100644 index 0000000..23c7455 --- /dev/null +++ b/src/Resources/crud/controller/Controller.tpl.php @@ -0,0 +1,69 @@ + + +namespace ; + + + +#[Route('')] +class extends AbstractController { + use FormControllerTrait; + + protected const ENTITY = ::class; + protected const TEMPLATE_PATH = '/'; + protected const ROUTE_PREFIX = '_'; + protected const FORM = ::class; + + public function __construct(private readonly EntityManagerInterface $entityManager) + { + } + generateRouteForControllerMethod('/', sprintf('%s_index', $route_name), ['GET']) ?> + + public function index( $): Response + { + return $this->render('/index.html.twig', [ + '' => $->findAll(), + ]); + } + + public function index(): Response + { + return $this->itemListView('', []); + } + + + generateRouteForControllerMethod('/new', sprintf('%s_new', $route_name), ['GET', 'POST']) ?> + repositoryHasSaveAndRemoveMethods($repository_full_class_name)) { ?> + public function new(Request $request, $): Response + + public function new(Request $request): Response + + { + return $this->itemCreate($request, ''); + } + + generateRouteForControllerMethod(sprintf('/{%s}', $entity_identifier), sprintf('%s_show', $route_name), ['GET']) ?> + public function show( $): Response + { + return $this->itemView($, ''); + } + + generateRouteForControllerMethod(sprintf('/{%s}/edit', $entity_identifier), sprintf('%s_edit', $route_name), ['GET', 'POST']) ?> + repositoryHasSaveAndRemoveMethods($repository_full_class_name)) { ?> + public function edit(Request $request, $, $): Response + + public function edit(Request $request, $): Response + + { + return $this->itemUpdate($request, $, ''); + } + + generateRouteForControllerMethod(sprintf('/{%s}', $entity_identifier), sprintf('%s_delete', $route_name), ['POST']) ?> + repositoryHasSaveAndRemoveMethods($repository_full_class_name)) { ?> + public function delete(Request $request, $, $): Response + + public function delete(Request $request, $): Response + + { + return $this->deleteCSRF($request, $); + } +} diff --git a/src/Resources/crud/templates/edit.tpl.php b/src/Resources/crud/templates/edit.tpl.php new file mode 100644 index 0000000..0b27f1f --- /dev/null +++ b/src/Resources/crud/templates/edit.tpl.php @@ -0,0 +1,30 @@ +{% extends 'form.html.twig' %} + +{% block title %} - Edit{% endblock %} + +{% block form %} +

Edit

+ +
+ +
+ +
+ {{ form_start(edit_form) }} + {{ form_widget(edit_form) }} + + {{ form_end(edit_form) }} + +
+ + +
+
+{% endblock %} diff --git a/src/Resources/crud/templates/index.tpl.php b/src/Resources/crud/templates/index.tpl.php new file mode 100644 index 0000000..495b6aa --- /dev/null +++ b/src/Resources/crud/templates/index.tpl.php @@ -0,0 +1,51 @@ +getHeadPrintCode($entity_class_name); ?> + +{% block body %} +

+ +
+ +
+ + + + + + + + + + + + {% for in %} + + + + + + + {% else %} + + + + {% endfor %} + +
 
+ + {{ getEntityFieldPrintCode($entity_twig_var_singular, $field) ?> }}
no records found
+{% endblock %} diff --git a/src/Resources/crud/templates/new.tpl.php b/src/Resources/crud/templates/new.tpl.php new file mode 100644 index 0000000..744e31f --- /dev/null +++ b/src/Resources/crud/templates/new.tpl.php @@ -0,0 +1,23 @@ +{% extends 'form.html.twig' %} + +{% block title %} - New{% endblock %} + +{% block form %} +

Add

+ +
+ +
+ +
+ {{ form_start(form) }} + {{ form_widget(form) }} + + {{ form_end(form) }} +
+ +{% endblock %} diff --git a/src/Resources/crud/templates/show.tpl.php b/src/Resources/crud/templates/show.tpl.php new file mode 100644 index 0000000..01c2cee --- /dev/null +++ b/src/Resources/crud/templates/show.tpl.php @@ -0,0 +1,40 @@ +{% extends 'form.html.twig' %} + +{% block title %}{% endblock %} + +{% block form %} +

Brand

+ +
+ + + +
+ + +
+ + +
+
+ +
+ + + + + + + + + +
{{ getEntityFieldPrintCode($entity_twig_var_singular, $field) ?> }}
+
+{% endblock %} diff --git a/src/Resources/help/MakeCrud.txt b/src/Resources/help/MakeCrud.txt new file mode 100644 index 0000000..6ecd38a --- /dev/null +++ b/src/Resources/help/MakeCrud.txt @@ -0,0 +1,5 @@ +The %command.name% command generates crud controller with templates for selected entity. + +php %command.full_name% BlogPost + +If the argument is missing, the command will ask for the entity class name interactively. diff --git a/symfony.lock b/symfony.lock index 2e0dc21..eb38487 100644 --- a/symfony.lock +++ b/symfony.lock @@ -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" },