├── LICENSE ├── composer.json ├── config ├── help │ ├── MakeAuth.txt │ ├── MakeCommand.txt │ ├── MakeController.txt │ ├── MakeCrud.txt │ ├── MakeDockerDatabase.txt │ ├── MakeEntity.txt │ ├── MakeFixture.txt │ ├── MakeForm.txt │ ├── MakeFunctionalTest.txt │ ├── MakeListener.txt │ ├── MakeMessage.txt │ ├── MakeMiddleware.txt │ ├── MakeMigration.txt │ ├── MakeRegistrationForm.txt │ ├── MakeResetPassword.txt │ ├── MakeScheduler.txt │ ├── MakeSerializerEncoder.txt │ ├── MakeSerializerNormalizer.txt │ ├── MakeStimulusController.txt │ ├── MakeSubscriber.txt │ ├── MakeTest.txt │ ├── MakeTwigExtension.txt │ ├── MakeUnitTest.txt │ ├── MakeUser.txt │ ├── MakeValidator.txt │ ├── MakeVoter.txt │ ├── MakeWebhook.txt │ ├── _WithTests.txt │ ├── _WithUid.txt │ └── security │ │ ├── MakeCustom.txt │ │ └── MakeFormLogin.txt ├── makers.xml ├── php-cs-fixer.config.php └── services.xml ├── docs └── index.rst ├── phpstan.dist.neon ├── src ├── ApplicationAwareMakerInterface.php ├── Command │ └── MakerCommand.php ├── Console │ └── MigrationDiffFilteredOutput.php ├── ConsoleStyle.php ├── DependencyBuilder.php ├── DependencyInjection │ └── CompilerPass │ │ ├── MakeCommandRegistrationPass.php │ │ ├── RemoveMissingParametersPass.php │ │ └── SetDoctrineAnnotatedPrefixesPass.php ├── Docker │ └── DockerDatabaseServices.php ├── Doctrine │ ├── BaseCollectionRelation.php │ ├── BaseRelation.php │ ├── DoctrineHelper.php │ ├── EntityClassGenerator.php │ ├── EntityDetails.php │ ├── EntityRegenerator.php │ ├── EntityRelation.php │ ├── ORMDependencyBuilder.php │ ├── RelationManyToMany.php │ ├── RelationManyToOne.php │ ├── RelationOneToMany.php │ ├── RelationOneToOne.php │ └── StaticReflectionService.php ├── Event │ └── ConsoleErrorSubscriber.php ├── EventRegistry.php ├── Exception │ └── RuntimeCommandException.php ├── FileManager.php ├── Generator.php ├── GeneratorTwigHelper.php ├── InputAwareMakerInterface.php ├── InputConfiguration.php ├── Maker │ ├── AbstractMaker.php │ ├── Common │ │ ├── CanGenerateTestsTrait.php │ │ ├── EntityIdTypeEnum.php │ │ ├── InstallDependencyTrait.php │ │ └── UidTrait.php │ ├── MakeAuthenticator.php │ ├── MakeCommand.php │ ├── MakeController.php │ ├── MakeCrud.php │ ├── MakeDockerDatabase.php │ ├── MakeEntity.php │ ├── MakeFixtures.php │ ├── MakeForm.php │ ├── MakeFunctionalTest.php │ ├── MakeListener.php │ ├── MakeMessage.php │ ├── MakeMessengerMiddleware.php │ ├── MakeMigration.php │ ├── MakeRegistrationForm.php │ ├── MakeResetPassword.php │ ├── MakeSchedule.php │ ├── MakeSerializerEncoder.php │ ├── MakeSerializerNormalizer.php │ ├── MakeStimulusController.php │ ├── MakeSubscriber.php │ ├── MakeTest.php │ ├── MakeTwigComponent.php │ ├── MakeTwigExtension.php │ ├── MakeUnitTest.php │ ├── MakeUser.php │ ├── MakeValidator.php │ ├── MakeVoter.php │ ├── MakeWebhook.php │ └── Security │ │ ├── MakeCustomAuthenticator.php │ │ └── MakeFormLogin.php ├── MakerBundle.php ├── MakerInterface.php ├── Renderer │ └── FormTypeRenderer.php ├── Resources │ └── bin │ │ └── php-cs-fixer-v3.49.0.phar ├── Security │ ├── InteractiveSecurityHelper.php │ ├── Model │ │ ├── Authenticator.php │ │ └── AuthenticatorType.php │ ├── SecurityConfigUpdater.php │ ├── SecurityControllerBuilder.php │ ├── UserClassBuilder.php │ └── UserClassConfiguration.php ├── Str.php ├── Test │ ├── MakerTestCase.php │ ├── MakerTestDetails.php │ ├── MakerTestEnvironment.php │ ├── MakerTestKernel.php │ ├── MakerTestProcess.php │ └── MakerTestRunner.php ├── Util │ ├── AutoloaderUtil.php │ ├── ClassDetails.php │ ├── ClassNameDetails.php │ ├── ClassNameValue.php │ ├── ClassSource │ │ └── Model │ │ │ ├── ClassData.php │ │ │ └── ClassProperty.php │ ├── ClassSourceManipulator.php │ ├── CliOutputHelper.php │ ├── ComposeFileManipulator.php │ ├── ComposerAutoloaderFinder.php │ ├── MakerFileLinkFormatter.php │ ├── PhpCompatUtil.php │ ├── PrettyPrinter.php │ ├── TemplateComponentGenerator.php │ ├── TemplateLinter.php │ ├── UseStatementGenerator.php │ ├── YamlManipulationFailedException.php │ └── YamlSourceManipulator.php └── Validator.php └── templates ├── Class.tpl.php ├── authenticator ├── EmptyAuthenticator.tpl.php ├── EmptySecurityController.tpl.php ├── LoginFormAuthenticator.tpl.php └── login_form.tpl.php ├── command └── Command.tpl.php ├── controller ├── Controller.tpl.php ├── test │ └── Test.tpl.php └── twig_template.tpl.php ├── crud ├── controller │ └── Controller.tpl.php ├── templates │ ├── _delete_form.tpl.php │ ├── _form.tpl.php │ ├── edit.tpl.php │ ├── index.tpl.php │ ├── new.tpl.php │ └── show.tpl.php └── test │ └── Test.EntityManager.tpl.php ├── doctrine ├── Entity.tpl.php ├── Fixtures.tpl.php ├── Repository.tpl.php └── broadcast_twig_template.tpl.php ├── event ├── Listener.tpl.php └── Subscriber.tpl.php ├── form └── Form.tpl.php ├── message ├── Message.tpl.php └── MessageHandler.tpl.php ├── middleware └── Middleware.tpl.php ├── registration ├── RegistrationController.tpl.php ├── Test.WithVerify.tpl.php ├── Test.WithoutVerify.tpl.php ├── twig_email.tpl.php └── twig_template.tpl.php ├── resetPassword ├── ChangePasswordForm.tpl.php ├── ResetPasswordController.tpl.php ├── ResetPasswordRequestForm.tpl.php ├── Test.ResetPasswordController.tpl.php ├── twig_check_email.tpl.php ├── twig_email.tpl.php ├── twig_request.tpl.php └── twig_reset.tpl.php ├── scheduler └── Schedule.tpl.php ├── security ├── UserProvider.tpl.php ├── Voter.tpl.php ├── custom │ └── Authenticator.tpl.php └── formLogin │ ├── LoginController.tpl.php │ ├── Test.LoginController.tpl.php │ └── login_form.tpl.php ├── serializer ├── Encoder.tpl.php └── Normalizer.tpl.php ├── stimulus └── Controller.tpl.php ├── test ├── ApiTestCase.tpl.php ├── Functional.tpl.php ├── KernelTestCase.tpl.php ├── PantherTestCase.tpl.php ├── TestCase.tpl.php ├── Unit.tpl.php └── WebTestCase.tpl.php ├── twig ├── Component.tpl.php ├── Extension.tpl.php ├── LiveComponent.tpl.php ├── Runtime.tpl.php └── component_template.tpl.php ├── validator ├── Constraint.tpl.php └── Validator.tpl.php ├── verifyEmail └── EmailVerifier.tpl.php └── webhook ├── RequestParser.tpl.php └── WebhookConsumer.tpl.php /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2004-2020 Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Symfony Maker helps you create empty commands, controllers, form classes, tests and more so you can forget about writing boilerplate code.", 3 | "homepage": "https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html", 4 | "name": "symfony/maker-bundle", 5 | "type": "symfony-bundle", 6 | "license": "MIT", 7 | "keywords": ["generator", "code generator", "scaffolding", "scaffold", "dev"], 8 | "authors": [ 9 | { 10 | "name": "Symfony Community", 11 | "homepage": "https://symfony.com/contributors" 12 | } 13 | ], 14 | "minimum-stability": "dev", 15 | "prefer-stable": true, 16 | "require": { 17 | "php": ">=8.1", 18 | "doctrine/inflector": "^2.0", 19 | "nikic/php-parser": "^5.0", 20 | "symfony/config": "^6.4|^7.0", 21 | "symfony/console": "^6.4|^7.0", 22 | "symfony/dependency-injection": "^6.4|^7.0", 23 | "symfony/deprecation-contracts": "^2.2|^3", 24 | "symfony/filesystem": "^6.4|^7.0", 25 | "symfony/finder": "^6.4|^7.0", 26 | "symfony/framework-bundle": "^6.4|^7.0", 27 | "symfony/http-kernel": "^6.4|^7.0", 28 | "symfony/process": "^6.4|^7.0" 29 | }, 30 | "require-dev": { 31 | "composer/semver": "^3.0", 32 | "doctrine/doctrine-bundle": "^2.5.0", 33 | "doctrine/orm": "^2.15|^3", 34 | "symfony/http-client": "^6.4|^7.0", 35 | "symfony/phpunit-bridge": "^6.4.1|^7.0", 36 | "symfony/security-core": "^6.4|^7.0", 37 | "symfony/security-http": "^6.4|^7.0", 38 | "symfony/yaml": "^6.4|^7.0", 39 | "twig/twig": "^3.0|^4.x-dev" 40 | }, 41 | "config": { 42 | "preferred-install": "dist", 43 | "sort-packages": true 44 | }, 45 | "conflict": { 46 | "doctrine/orm": "<2.15", 47 | "doctrine/doctrine-bundle": "<2.10" 48 | }, 49 | "autoload": { 50 | "psr-4": { "Symfony\\Bundle\\MakerBundle\\": "src/" } 51 | }, 52 | "autoload-dev": { 53 | "psr-4": { "Symfony\\Bundle\\MakerBundle\\Tests\\": "tests/" } 54 | }, 55 | "extra": { 56 | "branch-alias": { 57 | "dev-main": "1.x-dev" 58 | } 59 | }, 60 | "scripts": { 61 | "tools:upgrade": [ 62 | "@tools:upgrade:php-cs-fixer", 63 | "@tools:upgrade:phpstan", 64 | "@tools:upgrade:twigcs" 65 | ], 66 | "tools:upgrade:php-cs-fixer": "composer upgrade -W -d tools/php-cs-fixer", 67 | "tools:upgrade:phpstan": "composer upgrade -W -d tools/phpstan", 68 | "tools:upgrade:twigcs": "composer upgrade -W -d tools/twigcs", 69 | "tools:run": [ 70 | "@tools:run:php-cs-fixer", 71 | "@tools:run:phpstan", 72 | "@tools:run:twigcs" 73 | ], 74 | "tools:run:php-cs-fixer": "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix", 75 | "tools:run:phpstan": "tools/phpstan/vendor/bin/phpstan --memory-limit=1G", 76 | "tools:run:twigcs": "tools/twigcs/vendor/bin/twigcs --config tools/twigcs/.twigcs.dist.php" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /config/help/MakeAuth.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates various authentication systems, 2 | by asking questions. 3 | 4 | It can provide an empty authenticator, or a full login form authentication process. 5 | In both cases it also updates your security.yaml. 6 | For the login form, it also generates a controller and the Twig template. 7 | 8 | php %command.full_name% 9 | -------------------------------------------------------------------------------- /config/help/MakeCommand.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new command: 2 | 3 | php %command.full_name% app:do-something 4 | 5 | If the argument is missing, the command will ask for the command name interactively. 6 | -------------------------------------------------------------------------------- /config/help/MakeController.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new controller class. 2 | 3 | php %command.full_name% CoolStuffController 4 | 5 | If the argument is missing, the command will ask for the controller class name interactively. 6 | 7 | If you have the symfony/twig-bundle installed, a Twig template will also be 8 | generated for the controller. 9 | 10 | composer require symfony/twig-bundle 11 | 12 | You can also generate the controller alone, without template with this option: 13 | 14 | php %command.full_name% --no-template 15 | -------------------------------------------------------------------------------- /config/help/MakeCrud.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates crud controller with templates for selected entity. 2 | 3 | php %command.full_name% BlogPost 4 | 5 | If the argument is missing, the command will ask for the entity class name interactively. -------------------------------------------------------------------------------- /config/help/MakeDockerDatabase.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates or updates databases services in compose.yaml 2 | 3 | php %command.full_name% 4 | 5 | Supports MySQL, MariaDB and PostgreSQL 6 | -------------------------------------------------------------------------------- /config/help/MakeEntity.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command creates or updates an entity and repository class. 2 | 3 | php %command.full_name% BlogPost 4 | 5 | If the argument is missing, the command will ask for the entity class name interactively. 6 | 7 | You can also mark this class as an API Platform resource. A hypermedia CRUD API will 8 | automatically be available for this entity class: 9 | 10 | php %command.full_name% --api-resource 11 | 12 | Symfony can also broadcast all changes made to the entity to the client using Symfony 13 | UX Turbo. 14 | 15 | php %command.full_name% --broadcast 16 | 17 | You can also generate all the getter/setter/adder/remover methods 18 | for the properties of existing entities: 19 | 20 | php %command.full_name% --regenerate 21 | 22 | You can also *overwrite* any existing methods: 23 | 24 | php %command.full_name% --regenerate --overwrite 25 | -------------------------------------------------------------------------------- /config/help/MakeFixture.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new Doctrine fixtures class. 2 | 3 | php %command.full_name% AppFixtures 4 | 5 | If the argument is missing, the command will ask for a class interactively. 6 | -------------------------------------------------------------------------------- /config/help/MakeForm.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new form class. 2 | 3 | php %command.full_name% UserForm 4 | 5 | If the argument is missing, the command will ask for the form class interactively. 6 | 7 | You can optionally specify the bound class in a second argument. 8 | This can be the name of an entity like User 9 | 10 | php %command.full_name% UserForm User 11 | 12 | You can also specify a fully qualified name to another class like \App\Dto\UserData. 13 | Slashes must be escaped in the argument. 14 | 15 | php %command.full_name% UserForm \\App\\Dto\\UserData 16 | 17 | -------------------------------------------------------------------------------- /config/help/MakeFunctionalTest.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new functional test class. 2 | 3 | php %command.full_name% DefaultControllerTest 4 | 5 | If the argument is missing, the command will ask for the class name interactively. 6 | -------------------------------------------------------------------------------- /config/help/MakeListener.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new event subscriber class or a new event listener class. 2 | 3 | php %command.full_name% ExceptionListener 4 | 5 | If the argument is missing, the command will ask for the class name interactively. 6 | -------------------------------------------------------------------------------- /config/help/MakeMessage.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new message class & handler. 2 | 3 | php %command.full_name% EmailMessage 4 | 5 | If the argument is missing, the command will ask for the message class interactively. 6 | -------------------------------------------------------------------------------- /config/help/MakeMiddleware.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new Middleware class. 2 | 3 | php %command.full_name% CustomMiddleware 4 | 5 | If the argument is missing, the command will ask for the message class interactively. 6 | -------------------------------------------------------------------------------- /config/help/MakeMigration.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new migration: 2 | 3 | php %command.full_name% 4 | 5 | You can also generate a formatted migration with this option: 6 | 7 | php %command.full_name% --formatted 8 | -------------------------------------------------------------------------------- /config/help/MakeRegistrationForm.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a complete registration form, controller & template. 2 | 3 | php %command.full_name% 4 | 5 | The command will ask for several pieces of information to build your form. 6 | -------------------------------------------------------------------------------- /config/help/MakeResetPassword.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates all the files needed to implement 2 | a fully-functional & secure password reset system. 3 | 4 | The SymfonycastsResetPasswordBundle is required and can be added using composer: 5 | composer require symfonycasts/reset-password-bundle 6 | 7 | For more information on the reset-password-bundle check out: 8 | https://github.com/symfonycasts/reset-password-bundle 9 | 10 | %command.name% requires a user entity with an email property, 11 | email getter method, and a password setter method. Maker will ask for these 12 | interactively if they cannot be guessed. 13 | 14 | Maker will also update your reset-password.yaml configuration file 15 | if one exists. If you have customized the configuration file, maker will attempt 16 | to modify it accordingly but preserve your customizations. 17 | 18 | php %command.full_name% 19 | -------------------------------------------------------------------------------- /config/help/MakeScheduler.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a schedule to automate repeated 2 | tasks using Symfony's Scheduler Component. 3 | 4 | If the Scheduler Component is not installed, %command.name% will 5 | install it automatically using composer. You can of course do this manually by 6 | running composer require symfony/scheduler. 7 | 8 | php %command.full_name% 9 | -------------------------------------------------------------------------------- /config/help/MakeSerializerEncoder.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new serializer encoder class. 2 | 3 | php %command.full_name% YamlEncoder 4 | 5 | If the argument is missing, the command will ask for the class name interactively. 6 | -------------------------------------------------------------------------------- /config/help/MakeSerializerNormalizer.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new serializer normalizer class. 2 | 3 | php %command.full_name% UserNormalizer 4 | 5 | If the argument is missing, the command will ask for the class name interactively. 6 | -------------------------------------------------------------------------------- /config/help/MakeStimulusController.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new Stimulus controller. 2 | 3 | php %command.full_name% hello 4 | 5 | If the argument is missing, the command will ask for the controller name interactively. 6 | 7 | To generate a TypeScript file (instead of a JavaScript file) use the --typescript 8 | (or --ts) option: 9 | 10 | php %command.full_name% hello --typescript 11 | 12 | It will also interactively ask for values, targets, classes to add to the Stimulus 13 | controller (optional). 14 | 15 | php %command.full_name% 16 | -------------------------------------------------------------------------------- /config/help/MakeSubscriber.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new event subscriber class. 2 | 3 | php %command.full_name% ExceptionSubscriber 4 | 5 | If the argument is missing, the command will ask for the class name interactively. 6 | -------------------------------------------------------------------------------- /config/help/MakeTest.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new test class. 2 | 3 | php %command.full_name% TestCase BlogPostTest 4 | 5 | If the first argument is missing, the command will ask for the test type interactively. 6 | 7 | If the second argument is missing, the command will ask for the class name interactively. 8 | -------------------------------------------------------------------------------- /config/help/MakeTwigExtension.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new Twig extension with its runtime class. 2 | 3 | php %command.full_name% AppExtension 4 | 5 | If the argument is missing, the command will ask for the class name interactively. 6 | -------------------------------------------------------------------------------- /config/help/MakeUnitTest.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new unit test class. 2 | 3 | php %command.full_name% UtilTest 4 | 5 | If the argument is missing, the command will ask for the class name interactively. 6 | -------------------------------------------------------------------------------- /config/help/MakeUser.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new user class for security 2 | and updates your security.yaml file for it. It will also generate a user provider 3 | class if your situation needs a custom class. 4 | 5 | php %command.full_name% User 6 | 7 | If the argument is missing, the command will ask for the class name interactively. 8 | -------------------------------------------------------------------------------- /config/help/MakeValidator.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new validation constraint. 2 | 3 | php %command.full_name% EnabledValidator 4 | 5 | If the argument is missing, the command will ask for the constraint class name interactively. 6 | -------------------------------------------------------------------------------- /config/help/MakeVoter.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a new security voter. 2 | 3 | php %command.full_name% BlogPostVoter 4 | 5 | If the argument is missing, the command will ask for the class name interactively. 6 | -------------------------------------------------------------------------------- /config/help/MakeWebhook.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command creates a RequestParser, a WebhookHandler and adds the necessary configuration 2 | for a new Webhook. 3 | 4 | php %command.full_name% stripe 5 | 6 | If the argument is missing, the command will ask for the webhook name interactively. 7 | 8 | It will also interactively ask for the RequestMatchers to use for the RequestParser's getRequestMatcher function. 9 | -------------------------------------------------------------------------------- /config/help/_WithTests.txt: -------------------------------------------------------------------------------- 1 | To generate tailored PHPUnit tests, simply call: 2 | 3 | php %command.full_name% --with-tests 4 | 5 | This will generate a unit test in tests/ for you to review then use 6 | to test the new functionality of your app. 7 | -------------------------------------------------------------------------------- /config/help/_WithUid.txt: -------------------------------------------------------------------------------- 1 | Instead of using the default "int" type for the entity's "id", you can use the 2 | UUID type from Symfony's Uid component. 3 | https://symfony.com/doc/current/components/uid.html#storing-uuids-in-databases 4 | 5 | php %command.full_name% --with-uuid 6 | 7 | Or you can use the ULID type from Symfony's Uid component. 8 | https://symfony.com/doc/current/components/uid.html#storing-ulids-in-databases 9 | 10 | php %command.full_name% --with-ulid 11 | -------------------------------------------------------------------------------- /config/help/security/MakeCustom.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a simple custom authenticator 2 | class based off the example provided in: 3 | 4 | https://symfony.com/doc/current/security/custom_authenticator.html 5 | 6 | This will also update your security.yaml for the new custom authenticator. 7 | 8 | php %command.full_name% 9 | -------------------------------------------------------------------------------- /config/help/security/MakeFormLogin.txt: -------------------------------------------------------------------------------- 1 | The %command.name% command generates a controller and Twig template 2 | to allow users to login using the form_login authenticator. 3 | 4 | The controller name, and logout ability can be customized by answering the 5 | questions asked when running %command.name%. 6 | 7 | This will also update your security.yaml for the new authenticator. 8 | 9 | php %command.full_name% 10 | -------------------------------------------------------------------------------- /config/php-cs-fixer.config.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | /* 13 | * This PHP-CS-Fixer config file is used by the TemplateLinter for userland 14 | * code when say make:controller is run. If a user does not have a php-cs-fixer 15 | * config file, this one is used on the generated PHP files. 16 | * 17 | * It should not be confused by the root level .php-cs-fixer.dist.php config 18 | * which is used to maintain the MakerBundle codebase itself. 19 | */ 20 | return (new PhpCsFixer\Config()) 21 | ->setRules([ 22 | '@Symfony' => true, 23 | '@Symfony:risky' => true, 24 | 'native_function_invocation' => false, 25 | 'blank_line_before_statement' => ['statements' => ['break', 'case', 'continue', 'declare', 'default', 'do', 'exit', 'for', 'foreach', 'goto', 'if', 'include', 'include_once', 'phpdoc', 'require', 'require_once', 'return', 'switch', 'throw', 'try', 'while', 'yield', 'yield_from']], 26 | 'array_indentation' => true, 27 | ]) 28 | ->setRiskyAllowed(true) 29 | ; 30 | -------------------------------------------------------------------------------- /phpstan.dist.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 6 3 | bootstrapFiles: 4 | - vendor/autoload.php 5 | - tools/phpstan/includes/vendor/autoload.php 6 | paths: 7 | - src/Maker 8 | - tests/Command 9 | - tests/Docker 10 | - tests/Maker 11 | excludePaths: 12 | - tests/Doctrine/fixtures 13 | - tests/fixtures 14 | - tests/Security/fixtures 15 | - tests/Security/yaml_fixtures 16 | - tests/tmp 17 | - tests/Util/fixtures 18 | - tests/Util/yaml_fixtures 19 | ignoreErrors: 20 | - 21 | message: '#Property .+phpCompatUtil is never read, only written\.#' 22 | path: src/Maker/* 23 | 24 | - 25 | message: '#Class Symfony\\Bundle\\MakerBundle\\Maker\\LegacyApiTestCase not found#' 26 | path: src/Maker/* 27 | -------------------------------------------------------------------------------- /src/ApplicationAwareMakerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle; 13 | 14 | use Symfony\Component\Console\Application; 15 | 16 | /** 17 | * Implement this interface if your Maker needs access to the Application. 18 | * 19 | * @author Ryan Weaver 20 | */ 21 | interface ApplicationAwareMakerInterface 22 | { 23 | public function setApplication(Application $application); 24 | } 25 | -------------------------------------------------------------------------------- /src/Console/MigrationDiffFilteredOutput.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Console; 13 | 14 | use Symfony\Component\Console\Formatter\OutputFormatterInterface; 15 | use Symfony\Component\Console\Output\OutputInterface; 16 | 17 | class MigrationDiffFilteredOutput implements OutputInterface 18 | { 19 | private string $buffer = ''; 20 | private bool $previousLineWasRemoved = false; 21 | 22 | public function __construct( 23 | private OutputInterface $output, 24 | ) { 25 | } 26 | 27 | public function write($messages, bool $newline = false, $options = 0): void 28 | { 29 | $messages = $this->filterMessages($messages, $newline); 30 | 31 | $this->output->write($messages, $newline, $options); 32 | } 33 | 34 | public function writeln($messages, int $options = 0): void 35 | { 36 | $messages = $this->filterMessages($messages, true); 37 | 38 | $this->output->writeln($messages, $options); 39 | } 40 | 41 | public function setVerbosity(int $level): void 42 | { 43 | $this->output->setVerbosity($level); 44 | } 45 | 46 | public function setDecorated(bool $decorated): void 47 | { 48 | $this->output->setDecorated($decorated); 49 | } 50 | 51 | public function getVerbosity(): int 52 | { 53 | return $this->output->getVerbosity(); 54 | } 55 | 56 | public function isQuiet(): bool 57 | { 58 | return $this->output->isQuiet(); 59 | } 60 | 61 | public function isVerbose(): bool 62 | { 63 | return $this->output->isVerbose(); 64 | } 65 | 66 | public function isVeryVerbose(): bool 67 | { 68 | return $this->output->isVeryVerbose(); 69 | } 70 | 71 | public function isDebug(): bool 72 | { 73 | return $this->output->isDebug(); 74 | } 75 | 76 | public function isDecorated(): bool 77 | { 78 | return $this->output->isDecorated(); 79 | } 80 | 81 | public function setFormatter(OutputFormatterInterface $formatter): void 82 | { 83 | $this->output->setFormatter($formatter); 84 | } 85 | 86 | public function getFormatter(): OutputFormatterInterface 87 | { 88 | return $this->output->getFormatter(); 89 | } 90 | 91 | public function fetch(): string 92 | { 93 | return $this->buffer; 94 | } 95 | 96 | private function filterMessages($messages, bool $newLine) 97 | { 98 | if (!is_iterable($messages)) { 99 | $messages = [$messages]; 100 | } 101 | 102 | $hiddenPhrases = [ 103 | 'Generated new migration class', 104 | 'To run just this migration', 105 | 'To revert the migration you', 106 | ]; 107 | 108 | foreach ($messages as $key => $message) { 109 | $this->buffer .= $message; 110 | 111 | if ($newLine) { 112 | $this->buffer .= \PHP_EOL; 113 | } 114 | 115 | if ($this->previousLineWasRemoved && !trim($message)) { 116 | // hide a blank line after a filtered line 117 | unset($messages[$key]); 118 | $this->previousLineWasRemoved = false; 119 | 120 | continue; 121 | } 122 | 123 | $this->previousLineWasRemoved = false; 124 | foreach ($hiddenPhrases as $hiddenPhrase) { 125 | if (str_contains($message, $hiddenPhrase)) { 126 | $this->previousLineWasRemoved = true; 127 | unset($messages[$key]); 128 | 129 | break; 130 | } 131 | } 132 | } 133 | 134 | return array_values($messages); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/ConsoleStyle.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle; 13 | 14 | use Symfony\Component\Console\Input\InputInterface; 15 | use Symfony\Component\Console\Output\OutputInterface; 16 | use Symfony\Component\Console\Style\SymfonyStyle; 17 | 18 | /** 19 | * @author Javier Eguiluz 20 | * @author Ryan Weaver 21 | */ 22 | final class ConsoleStyle extends SymfonyStyle 23 | { 24 | public function __construct( 25 | InputInterface $input, 26 | private OutputInterface $output, 27 | ) { 28 | parent::__construct($input, $output); 29 | } 30 | 31 | public function success($message): void 32 | { 33 | $this->writeln('OK '.$message); 34 | } 35 | 36 | public function comment($message): void 37 | { 38 | $this->text($message); 39 | } 40 | 41 | public function getOutput(): OutputInterface 42 | { 43 | return $this->output; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/DependencyInjection/CompilerPass/MakeCommandRegistrationPass.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass; 13 | 14 | use Symfony\Bundle\MakerBundle\Command\MakerCommand; 15 | use Symfony\Bundle\MakerBundle\MakerInterface; 16 | use Symfony\Bundle\MakerBundle\Str; 17 | use Symfony\Component\Console\Command\LazyCommand; 18 | use Symfony\Component\DependencyInjection\ChildDefinition; 19 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 20 | use Symfony\Component\DependencyInjection\ContainerBuilder; 21 | use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; 22 | use Symfony\Component\DependencyInjection\Reference; 23 | 24 | class MakeCommandRegistrationPass implements CompilerPassInterface 25 | { 26 | public const MAKER_TAG = 'maker.command'; 27 | 28 | public function process(ContainerBuilder $container): void 29 | { 30 | foreach ($container->findTaggedServiceIds(self::MAKER_TAG) as $id => $tags) { 31 | $def = $container->getDefinition($id); 32 | if ($def->isDeprecated()) { 33 | continue; 34 | } 35 | 36 | $class = $container->getParameterBag()->resolveValue($def->getClass()); 37 | if (!is_subclass_of($class, MakerInterface::class)) { 38 | throw new InvalidArgumentException(\sprintf('Service "%s" must implement interface "%s".', $id, MakerInterface::class)); 39 | } 40 | 41 | $commandDefinition = new ChildDefinition('maker.auto_command.abstract'); 42 | $commandDefinition->setClass(MakerCommand::class); 43 | $commandDefinition->replaceArgument(0, new Reference($id)); 44 | 45 | $tagAttributes = ['command' => $class::getCommandName()]; 46 | 47 | if (!method_exists($class, 'getCommandDescription')) { 48 | // no-op 49 | } elseif (class_exists(LazyCommand::class)) { 50 | $tagAttributes['description'] = $class::getCommandDescription(); 51 | } else { 52 | $commandDefinition->addMethodCall('setDescription', [$class::getCommandDescription()]); 53 | } 54 | 55 | $commandDefinition->addTag('console.command', $tagAttributes); 56 | 57 | /* 58 | * @deprecated remove this block when removing make:unit-test and make:functional-test 59 | */ 60 | if (method_exists($class, 'getCommandAliases')) { 61 | foreach ($class::getCommandAliases() as $alias) { 62 | $commandDefinition->addTag('console.command', ['command' => $alias, 'description' => 'Deprecated alias of "make:test"']); 63 | } 64 | } 65 | 66 | /* 67 | * @deprecated remove this block when removing make:subscriber 68 | */ 69 | if (method_exists($class, 'getCommandAlias')) { 70 | $alias = $class::getCommandAlias(); 71 | $commandDefinition->addTag('console.command', ['command' => $alias, 'description' => 'Deprecated alias of "make:listener"']); 72 | } 73 | 74 | $container->setDefinition(\sprintf('maker.auto_command.%s', Str::asTwigVariable($class::getCommandName())), $commandDefinition); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/DependencyInjection/CompilerPass/RemoveMissingParametersPass.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass; 13 | 14 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 15 | use Symfony\Component\DependencyInjection\ContainerBuilder; 16 | 17 | /** 18 | * Removes injected parameter arguments if they don't exist in this app. 19 | * 20 | * @author Ryan Weaver 21 | */ 22 | class RemoveMissingParametersPass implements CompilerPassInterface 23 | { 24 | public function process(ContainerBuilder $container): void 25 | { 26 | if (!$container->hasParameter('twig.default_path')) { 27 | $container->getDefinition('maker.file_manager') 28 | ->replaceArgument(4, null); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/DependencyInjection/CompilerPass/SetDoctrineAnnotatedPrefixesPass.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass; 13 | 14 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 15 | use Symfony\Component\DependencyInjection\ContainerBuilder; 16 | use Symfony\Component\DependencyInjection\Definition; 17 | use Symfony\Component\DependencyInjection\Reference; 18 | 19 | class SetDoctrineAnnotatedPrefixesPass implements CompilerPassInterface 20 | { 21 | public function process(ContainerBuilder $container): void 22 | { 23 | $annotatedPrefixes = null; 24 | 25 | foreach ($container->findTaggedServiceIds('doctrine.orm.configuration') as $id => $tags) { 26 | $metadataDriverImpl = null; 27 | foreach ($container->getDefinition($id)->getMethodCalls() as [$method, $arguments]) { 28 | if ('setMetadataDriverImpl' === $method) { 29 | $metadataDriverImpl = $container->getDefinition($arguments[0]); 30 | break; 31 | } 32 | } 33 | 34 | if (null === $metadataDriverImpl || !preg_match('/^doctrine\.orm\.(.+)_configuration$/D', $id, $m)) { 35 | continue; 36 | } 37 | 38 | $managerName = $m[1]; 39 | $methodCalls = $metadataDriverImpl->getMethodCalls(); 40 | 41 | foreach ($methodCalls as $i => [$method, $arguments]) { 42 | if ('addDriver' !== $method) { 43 | continue; 44 | } 45 | 46 | if ($arguments[0] instanceof Definition) { 47 | $class = $arguments[0]->getClass(); 48 | 49 | $id = \sprintf('.%d_doctrine_metadata_driver~%s', $i, ContainerBuilder::hash($arguments)); 50 | $container->setDefinition($id, $arguments[0]); 51 | $arguments[0] = new Reference($id); 52 | $methodCalls[$i] = [$method, $arguments]; 53 | } 54 | 55 | $annotatedPrefixes[$managerName][] = [ 56 | $arguments[1], 57 | new Reference($arguments[0]), 58 | ]; 59 | } 60 | 61 | $metadataDriverImpl->setMethodCalls($methodCalls); 62 | } 63 | 64 | if (null !== $annotatedPrefixes) { 65 | $container->getDefinition('maker.doctrine_helper')->setArgument(2, $annotatedPrefixes); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Docker/DockerDatabaseServices.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Docker; 13 | 14 | use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; 15 | 16 | /** 17 | * @author Jesse Rushlow 18 | * 19 | * @internal 20 | */ 21 | final class DockerDatabaseServices 22 | { 23 | /** 24 | * @throws RuntimeCommandException 25 | */ 26 | public static function getDatabaseSkeleton(string $name, string $version): array 27 | { 28 | switch ($name) { 29 | case 'mariadb': 30 | return [ 31 | 'image' => \sprintf('mariadb:%s', $version), 32 | 'environment' => [ 33 | 'MYSQL_ROOT_PASSWORD' => 'password', 34 | 'MYSQL_DATABASE' => 'main', 35 | ], 36 | ]; 37 | case 'mysql': 38 | return [ 39 | 'image' => \sprintf('mysql:%s', $version), 40 | 'environment' => [ 41 | 'MYSQL_ROOT_PASSWORD' => 'password', 42 | 'MYSQL_DATABASE' => 'main', 43 | ], 44 | ]; 45 | case 'postgres': 46 | return [ 47 | 'image' => \sprintf('postgres:%s', $version), 48 | 'environment' => [ 49 | 'POSTGRES_PASSWORD' => 'main', 50 | 'POSTGRES_USER' => 'main', 51 | 'POSTGRES_DB' => 'main', 52 | ], 53 | ]; 54 | } 55 | 56 | self::throwInvalidDatabase($name); 57 | } 58 | 59 | /** 60 | * @throws RuntimeCommandException 61 | */ 62 | public static function getDefaultPorts(string $name): array 63 | { 64 | switch ($name) { 65 | case 'mariadb': 66 | case 'mysql': 67 | return ['3306']; 68 | case 'postgres': 69 | return ['5432']; 70 | } 71 | 72 | self::throwInvalidDatabase($name); 73 | } 74 | 75 | public static function getSuggestedServiceVersion(string $name): string 76 | { 77 | if ('postgres' === $name) { 78 | return 'alpine'; 79 | } 80 | 81 | return 'latest'; 82 | } 83 | 84 | public static function getMissingExtensionName(string $name): ?string 85 | { 86 | $driver = match ($name) { 87 | 'mariadb', 'mysql' => 'mysql', 88 | 'postgres' => 'pgsql', 89 | default => self::throwInvalidDatabase($name), 90 | }; 91 | 92 | if (!\in_array($driver, \PDO::getAvailableDrivers(), true)) { 93 | return $driver; 94 | } 95 | 96 | return null; 97 | } 98 | 99 | /** 100 | * @throws RuntimeCommandException 101 | */ 102 | private static function throwInvalidDatabase(string $name): never 103 | { 104 | throw new RuntimeCommandException(\sprintf('%s is not a valid / supported docker database type.', $name)); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Doctrine/BaseCollectionRelation.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Doctrine; 13 | 14 | use Symfony\Bundle\MakerBundle\Str; 15 | 16 | /** 17 | * @internal 18 | */ 19 | abstract class BaseCollectionRelation extends BaseRelation 20 | { 21 | abstract public function getTargetSetterMethodName(): string; 22 | 23 | public function getAdderMethodName(): string 24 | { 25 | return 'add'.Str::asCamelCase(Str::pluralCamelCaseToSingular($this->getPropertyName())); 26 | } 27 | 28 | public function getRemoverMethodName(): string 29 | { 30 | return 'remove'.Str::asCamelCase(Str::pluralCamelCaseToSingular($this->getPropertyName())); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Doctrine/BaseRelation.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Doctrine; 13 | 14 | /** 15 | * @internal 16 | */ 17 | abstract class BaseRelation 18 | { 19 | public function __construct( 20 | private string $propertyName, 21 | private string $targetClassName, 22 | private ?string $targetPropertyName = null, 23 | private bool $isSelfReferencing = false, 24 | private bool $mapInverseRelation = true, 25 | private bool $avoidSetter = false, 26 | private bool $isCustomReturnTypeNullable = false, 27 | private ?string $customReturnType = null, 28 | private bool $isOwning = false, 29 | private bool $orphanRemoval = false, 30 | private bool $isNullable = false, 31 | ) { 32 | } 33 | 34 | public function getPropertyName(): string 35 | { 36 | return $this->propertyName; 37 | } 38 | 39 | public function getTargetClassName(): string 40 | { 41 | return $this->targetClassName; 42 | } 43 | 44 | public function getTargetPropertyName(): ?string 45 | { 46 | return $this->targetPropertyName; 47 | } 48 | 49 | public function isSelfReferencing(): bool 50 | { 51 | return $this->isSelfReferencing; 52 | } 53 | 54 | public function getMapInverseRelation(): bool 55 | { 56 | return $this->mapInverseRelation; 57 | } 58 | 59 | public function shouldAvoidSetter(): bool 60 | { 61 | return $this->avoidSetter; 62 | } 63 | 64 | public function getCustomReturnType(): ?string 65 | { 66 | return $this->customReturnType; 67 | } 68 | 69 | public function isCustomReturnTypeNullable(): bool 70 | { 71 | return $this->isCustomReturnTypeNullable; 72 | } 73 | 74 | public function isOwning(): bool 75 | { 76 | return $this->isOwning; 77 | } 78 | 79 | public function getOrphanRemoval(): bool 80 | { 81 | return $this->orphanRemoval; 82 | } 83 | 84 | public function isNullable(): bool 85 | { 86 | return $this->isNullable; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Doctrine/EntityDetails.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Doctrine; 13 | 14 | use Doctrine\Persistence\Mapping\ClassMetadata; 15 | use Symfony\Bridge\Doctrine\Form\Type\EntityType; 16 | 17 | /** 18 | * @author Sadicov Vladimir 19 | * 20 | * @internal 21 | */ 22 | final class EntityDetails 23 | { 24 | public function __construct( 25 | private ClassMetadata $metadata, 26 | ) { 27 | } 28 | 29 | public function getRepositoryClass(): ?string 30 | { 31 | return $this->metadata->customRepositoryClassName; 32 | } 33 | 34 | public function getIdentifier() 35 | { 36 | return $this->metadata->identifier[0]; 37 | } 38 | 39 | public function getDisplayFields(): array 40 | { 41 | return $this->metadata->fieldMappings; 42 | } 43 | 44 | public function getFormFields(): array 45 | { 46 | $fields = (array) $this->metadata->fieldNames; 47 | // Remove the primary key field if it's not managed manually 48 | if (!$this->metadata->isIdentifierNatural()) { 49 | $fields = array_diff($fields, $this->metadata->identifier); 50 | } 51 | $fields = array_values($fields); 52 | 53 | if (!empty($this->metadata->embeddedClasses)) { 54 | foreach (array_keys($this->metadata->embeddedClasses) as $embeddedClassKey) { 55 | $fields = array_filter($fields, static fn ($v) => !str_starts_with($v, $embeddedClassKey.'.')); 56 | } 57 | } 58 | 59 | $fieldsWithTypes = []; 60 | foreach ($fields as $field) { 61 | $fieldsWithTypes[$field] = null; 62 | } 63 | 64 | foreach ($this->metadata->fieldMappings as $fieldName => $fieldMapping) { 65 | $propType = DoctrineHelper::getPropertyTypeForColumn($fieldMapping['type']); 66 | if (($propType === '\\'.\DateTimeImmutable::class) 67 | || ($propType === '\\'.\DateTimeInterface::class)) { 68 | $fieldsWithTypes[$fieldName] = [ 69 | 'type' => null, 70 | 'options_code' => "'widget' => 'single_text'", 71 | ]; 72 | } 73 | } 74 | foreach ($this->metadata->associationMappings as $fieldName => $relation) { 75 | if (\Doctrine\ORM\Mapping\ClassMetadata::ONE_TO_MANY === $relation['type']) { 76 | continue; 77 | } 78 | $fieldsWithTypes[$fieldName] = [ 79 | 'type' => EntityType::class, 80 | 'options_code' => \sprintf('\'class\' => %s::class,', $relation['targetEntity'])."\n 'choice_label' => 'id',", 81 | 'extra_use_classes' => [$relation['targetEntity']], 82 | ]; 83 | if (\Doctrine\ORM\Mapping\ClassMetadata::MANY_TO_MANY === $relation['type']) { 84 | $fieldsWithTypes[$fieldName]['options_code'] .= "\n 'multiple' => true,"; 85 | } 86 | } 87 | 88 | return $fieldsWithTypes; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Doctrine/ORMDependencyBuilder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Doctrine; 13 | 14 | use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; 15 | use Doctrine\ORM\Mapping\Column; 16 | use Symfony\Bundle\MakerBundle\DependencyBuilder; 17 | 18 | /** 19 | * @internal 20 | */ 21 | final class ORMDependencyBuilder 22 | { 23 | /** 24 | * Central method to add dependencies needed for Doctrine ORM. 25 | */ 26 | public static function buildDependencies(DependencyBuilder $dependencies): void 27 | { 28 | $classes = [ 29 | // guarantee DoctrineBundle 30 | DoctrineBundle::class, 31 | // guarantee ORM 32 | Column::class, 33 | ]; 34 | 35 | foreach ($classes as $class) { 36 | $dependencies->addClassDependency( 37 | $class, 38 | 'orm' 39 | ); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Doctrine/RelationManyToMany.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Doctrine; 13 | 14 | use Doctrine\ORM\Mapping\ManyToManyInverseSideMapping; 15 | use Doctrine\ORM\Mapping\ManyToManyOwningSideMapping; 16 | use Symfony\Bundle\MakerBundle\Str; 17 | 18 | /** 19 | * @internal 20 | */ 21 | final class RelationManyToMany extends BaseCollectionRelation 22 | { 23 | public function getTargetSetterMethodName(): string 24 | { 25 | return 'add'.Str::asCamelCase(Str::pluralCamelCaseToSingular($this->getTargetPropertyName())); 26 | } 27 | 28 | public function getTargetRemoverMethodName(): string 29 | { 30 | return 'remove'.Str::asCamelCase(Str::pluralCamelCaseToSingular($this->getTargetPropertyName())); 31 | } 32 | 33 | public static function createFromObject(ManyToManyInverseSideMapping|ManyToManyOwningSideMapping|array $mapping): self 34 | { 35 | /* @legacy Remove conditional when ORM 2.x is no longer supported! */ 36 | if (\is_array($mapping)) { 37 | return new self( 38 | propertyName: $mapping['fieldName'], 39 | targetClassName: $mapping['targetEntity'], 40 | targetPropertyName: $mapping['mappedBy'], 41 | mapInverseRelation: !$mapping['isOwningSide'] || null !== $mapping['inversedBy'], 42 | isOwning: $mapping['isOwningSide'], 43 | ); 44 | } 45 | 46 | if ($mapping instanceof ManyToManyOwningSideMapping) { 47 | return new self( 48 | propertyName: $mapping->fieldName, 49 | targetClassName: $mapping->targetEntity, 50 | targetPropertyName: $mapping->inversedBy, 51 | mapInverseRelation: (null !== $mapping->inversedBy), 52 | isOwning: $mapping->isOwningSide(), 53 | ); 54 | } 55 | 56 | return new self( 57 | propertyName: $mapping->fieldName, 58 | targetClassName: $mapping->targetEntity, 59 | targetPropertyName: $mapping->mappedBy, 60 | mapInverseRelation: true, 61 | isOwning: $mapping->isOwningSide(), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Doctrine/RelationManyToOne.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Doctrine; 13 | 14 | use Doctrine\ORM\Mapping\ManyToOneAssociationMapping; 15 | 16 | /** 17 | * @internal 18 | */ 19 | final class RelationManyToOne extends BaseRelation 20 | { 21 | public static function createFromObject(ManyToOneAssociationMapping|array $mapping): self 22 | { 23 | /* @legacy Remove conditional when ORM 2.x is no longer supported! */ 24 | if (\is_array($mapping)) { 25 | return new self( 26 | propertyName: $mapping['fieldName'], 27 | targetClassName: $mapping['targetEntity'], 28 | targetPropertyName: $mapping['inversedBy'], 29 | mapInverseRelation: null !== $mapping['inversedBy'], 30 | isOwning: true, 31 | isNullable: $mapping['joinColumns'][0]['nullable'] ?? true, 32 | ); 33 | } 34 | 35 | return new self( 36 | propertyName: $mapping->fieldName, 37 | targetClassName: $mapping->targetEntity, 38 | targetPropertyName: $mapping->inversedBy, 39 | mapInverseRelation: null !== $mapping->inversedBy, 40 | isOwning: true, 41 | isNullable: $mapping->joinColumns[0]->nullable ?? true, 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Doctrine/RelationOneToMany.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Doctrine; 13 | 14 | use Doctrine\ORM\Mapping\OneToManyAssociationMapping; 15 | use Symfony\Bundle\MakerBundle\Str; 16 | 17 | /** 18 | * @internal 19 | */ 20 | final class RelationOneToMany extends BaseCollectionRelation 21 | { 22 | public function getTargetGetterMethodName(): string 23 | { 24 | return 'get'.Str::asCamelCase($this->getTargetPropertyName()); 25 | } 26 | 27 | public function getTargetSetterMethodName(): string 28 | { 29 | return 'set'.Str::asCamelCase($this->getTargetPropertyName()); 30 | } 31 | 32 | public function isMapInverseRelation(): bool 33 | { 34 | throw new \Exception('OneToMany IS the inverse side!'); 35 | } 36 | 37 | public static function createFromObject(OneToManyAssociationMapping|array $mapping): self 38 | { 39 | /* @legacy Remove conditional when ORM 2.x is no longer supported! */ 40 | if (\is_array($mapping)) { 41 | return new self( 42 | propertyName: $mapping['fieldName'], 43 | targetClassName: $mapping['targetEntity'], 44 | targetPropertyName: $mapping['mappedBy'], 45 | orphanRemoval: $mapping['orphanRemoval'], 46 | ); 47 | } 48 | 49 | return new self( 50 | propertyName: $mapping->fieldName, 51 | targetClassName: $mapping->targetEntity, 52 | targetPropertyName: $mapping->mappedBy, 53 | orphanRemoval: $mapping->orphanRemoval, 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Doctrine/RelationOneToOne.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Doctrine; 13 | 14 | use Doctrine\ORM\Mapping\OneToOneInverseSideMapping; 15 | use Doctrine\ORM\Mapping\OneToOneOwningSideMapping; 16 | use Symfony\Bundle\MakerBundle\Str; 17 | 18 | /** 19 | * @internal 20 | */ 21 | final class RelationOneToOne extends BaseRelation 22 | { 23 | public function getTargetGetterMethodName(): string 24 | { 25 | return 'get'.Str::asCamelCase($this->getTargetPropertyName()); 26 | } 27 | 28 | public function getTargetSetterMethodName(): string 29 | { 30 | return 'set'.Str::asCamelCase($this->getTargetPropertyName()); 31 | } 32 | 33 | public static function createFromObject(OneToOneInverseSideMapping|OneToOneOwningSideMapping|array $mapping): self 34 | { 35 | /* @legacy Remove conditional when ORM 2.x is no longer supported! */ 36 | if (\is_array($mapping)) { 37 | return new self( 38 | propertyName: $mapping['fieldName'], 39 | targetClassName: $mapping['targetEntity'], 40 | targetPropertyName: $mapping['isOwningSide'] ? $mapping['inversedBy'] : $mapping['mappedBy'], 41 | mapInverseRelation: !$mapping['isOwningSide'] || null !== $mapping['inversedBy'], 42 | isOwning: $mapping['isOwningSide'], 43 | isNullable: $mapping['joinColumns'][0]['nullable'] ?? true, 44 | ); 45 | } 46 | 47 | if ($mapping instanceof OneToOneOwningSideMapping) { 48 | return new self( 49 | propertyName: $mapping->fieldName, 50 | targetClassName: $mapping->targetEntity, 51 | targetPropertyName: $mapping->inversedBy, 52 | mapInverseRelation: (null !== $mapping->inversedBy), 53 | isOwning: true, 54 | isNullable: $mapping->joinColumns[0]->nullable ?? true, 55 | ); 56 | } 57 | 58 | return new self( 59 | propertyName: $mapping->fieldName, 60 | targetClassName: $mapping->targetEntity, 61 | targetPropertyName: $mapping->mappedBy, 62 | mapInverseRelation: true, 63 | isOwning: false, 64 | isNullable: $mapping->joinColumns[0]->nullable ?? true, 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Doctrine/StaticReflectionService.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Doctrine; 13 | 14 | use Doctrine\Persistence\Mapping\ReflectionService; 15 | 16 | /** 17 | * @internal replacing removed Doctrine\Persistence\Mapping\StaticReflectionService 18 | */ 19 | final class StaticReflectionService implements ReflectionService 20 | { 21 | public function getParentClasses($class): array 22 | { 23 | return []; 24 | } 25 | 26 | public function getClassShortName($class): string 27 | { 28 | $nsSeparatorLastPosition = strrpos($class, '\\'); 29 | 30 | if (false !== $nsSeparatorLastPosition) { 31 | $class = substr($class, $nsSeparatorLastPosition + 1); 32 | } 33 | 34 | return $class; 35 | } 36 | 37 | public function getClassNamespace($class): string 38 | { 39 | $namespace = ''; 40 | 41 | if (str_contains($class, '\\')) { 42 | $namespace = strrev(substr(strrev($class), (int) strpos(strrev($class), '\\') + 1)); 43 | } 44 | 45 | return $namespace; 46 | } 47 | 48 | public function getClass($class): \ReflectionClass 49 | { 50 | return new \ReflectionClass($class); 51 | } 52 | 53 | public function getAccessibleProperty($class, $property): ?\ReflectionProperty 54 | { 55 | return null; 56 | } 57 | 58 | public function hasPublicMethod($class, $method): bool 59 | { 60 | return true; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Event/ConsoleErrorSubscriber.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Event; 13 | 14 | use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; 15 | use Symfony\Component\Console\ConsoleEvents; 16 | use Symfony\Component\Console\Event\ConsoleErrorEvent; 17 | use Symfony\Component\Console\Event\ConsoleTerminateEvent; 18 | use Symfony\Component\Console\Style\SymfonyStyle; 19 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; 20 | 21 | /** 22 | * Prints certain exceptions in a pretty way and silences normal exception handling. 23 | * 24 | * @author Ryan Weaver 25 | */ 26 | final class ConsoleErrorSubscriber implements EventSubscriberInterface 27 | { 28 | private bool $setExitCode = false; 29 | 30 | public function onConsoleError(ConsoleErrorEvent $event): void 31 | { 32 | if (!$event->getError() instanceof RuntimeCommandException) { 33 | return; 34 | } 35 | 36 | // prevent any visual logging from appearing 37 | $event->stopPropagation(); 38 | // prevent the exception from actually being thrown 39 | $event->setExitCode(0); 40 | $this->setExitCode = true; 41 | 42 | $io = new SymfonyStyle($event->getInput(), $event->getOutput()); 43 | $io->error($event->getError()->getMessage()); 44 | } 45 | 46 | public function onConsoleTerminate(ConsoleTerminateEvent $event): void 47 | { 48 | if (!$this->setExitCode) { 49 | return; 50 | } 51 | 52 | // finally set a non-zero exit code 53 | $event->setExitCode(1); 54 | } 55 | 56 | public static function getSubscribedEvents(): array 57 | { 58 | return [ 59 | ConsoleEvents::ERROR => 'onConsoleError', 60 | ConsoleEvents::TERMINATE => 'onConsoleTerminate', 61 | ]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Exception/RuntimeCommandException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Exception; 13 | 14 | use Symfony\Component\Console\Exception\ExceptionInterface; 15 | 16 | /** 17 | * An exception whose output is displayed as a clean error. 18 | * 19 | * @author Ryan Weaver 20 | */ 21 | final class RuntimeCommandException extends \RuntimeException implements ExceptionInterface 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/GeneratorTwigHelper.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle; 13 | 14 | /** 15 | * @author Sadicov Vladimir 16 | */ 17 | final class GeneratorTwigHelper 18 | { 19 | public function __construct( 20 | private FileManager $fileManager, 21 | ) { 22 | } 23 | 24 | public function getEntityFieldPrintCode($entity, $field): string 25 | { 26 | $twigField = preg_replace_callback('/(?!^)_([a-z0-9])/', static fn ($s) => strtoupper($s[1]), $field['fieldName']); 27 | $printCode = $entity.'.'.str_replace('_', '', $twigField); 28 | 29 | match ($field['type']) { 30 | 'datetimetz_immutable', 'datetimetz' => $printCode .= ' ? '.$printCode.'|date(\'Y-m-d H:i:s T\') : \'\'', 31 | 'datetime_immutable', 'datetime' => $printCode .= ' ? '.$printCode.'|date(\'Y-m-d H:i:s\') : \'\'', 32 | 'dateinterval' => $printCode .= ' ? '.$printCode.'.format(\'%y year(s), %m month(s), %d day(s)\') : \'\'', 33 | 'date_immutable', 'date' => $printCode .= ' ? '.$printCode.'|date(\'Y-m-d\') : \'\'', 34 | 'time_immutable', 'time' => $printCode .= ' ? '.$printCode.'|date(\'H:i:s\') : \'\'', 35 | 'json' => $printCode .= ' ? '.$printCode.'|json_encode : \'\'', 36 | 'array' => $printCode .= ' ? '.$printCode.'|join(\', \') : \'\'', 37 | 'boolean' => $printCode .= ' ? \'Yes\' : \'No\'', 38 | default => $printCode, 39 | }; 40 | 41 | return $printCode; 42 | } 43 | 44 | public function getHeadPrintCode($title): string 45 | { 46 | if ($this->fileManager->fileExists($this->fileManager->getPathForTemplate('base.html.twig'))) { 47 | return << 57 | 58 | $title 59 | 60 | HTML; 61 | } 62 | 63 | public function getFileLink($path, $text = null, $line = 0): string 64 | { 65 | trigger_deprecation('symfony/maker-bundle', 'v1.53.0', 'getFileLink() is deprecated and will be removed in the future.'); 66 | 67 | $text = $text ?: $path; 68 | 69 | return "$text"; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/InputAwareMakerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle; 13 | 14 | use Symfony\Component\Console\Input\InputInterface; 15 | 16 | /** 17 | * Lets the configureDependencies method access to the command's input. 18 | * 19 | * @author Kévin Dunglas 20 | */ 21 | interface InputAwareMakerInterface extends MakerInterface 22 | { 23 | public function configureDependencies(DependencyBuilder $dependencies, ?InputInterface $input = null); 24 | } 25 | -------------------------------------------------------------------------------- /src/InputConfiguration.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle; 13 | 14 | final class InputConfiguration 15 | { 16 | private array $nonInteractiveArguments = []; 17 | 18 | /** 19 | * Call in MakerInterface::configureCommand() to disable the automatic interactive 20 | * prompt for an argument. 21 | */ 22 | public function setArgumentAsNonInteractive(string $argumentName): void 23 | { 24 | $this->nonInteractiveArguments[] = $argumentName; 25 | } 26 | 27 | public function getNonInteractiveArguments(): array 28 | { 29 | return $this->nonInteractiveArguments; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Maker/AbstractMaker.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Maker; 13 | 14 | use Symfony\Bundle\MakerBundle\ConsoleStyle; 15 | use Symfony\Bundle\MakerBundle\DependencyBuilder; 16 | use Symfony\Bundle\MakerBundle\MakerInterface; 17 | use Symfony\Component\Console\Command\Command; 18 | use Symfony\Component\Console\Input\InputInterface; 19 | 20 | /** 21 | * Convenient abstract class for makers. 22 | */ 23 | abstract class AbstractMaker implements MakerInterface 24 | { 25 | /** 26 | * @return void 27 | */ 28 | public function interact(InputInterface $input, ConsoleStyle $io, Command $command) 29 | { 30 | } 31 | 32 | /** 33 | * @return void 34 | */ 35 | protected function writeSuccessMessage(ConsoleStyle $io) 36 | { 37 | $io->newLine(); 38 | $io->writeln(' '); 39 | $io->writeln(' Success! '); 40 | $io->writeln(' '); 41 | $io->newLine(); 42 | } 43 | 44 | /** @param array $dependencies */ 45 | protected function addDependencies(array $dependencies, ?string $message = null): string 46 | { 47 | $dependencyBuilder = new DependencyBuilder(); 48 | 49 | foreach ($dependencies as $class => $name) { 50 | $dependencyBuilder->addClassDependency($class, $name); 51 | } 52 | 53 | return $dependencyBuilder->getMissingPackagesMessage( 54 | static::getCommandName(), 55 | $message 56 | ); 57 | } 58 | 59 | /** 60 | * Get the help file contents needed for "setHelp()" of a maker. 61 | * 62 | * @param string $helpFileName the filename (omit path) of the help file located in config/help/ 63 | * e.g. MakeController.txt 64 | * 65 | * @internal 66 | */ 67 | final protected function getHelpFileContents(string $helpFileName): string 68 | { 69 | return file_get_contents(\sprintf('%s/config/help/%s', \dirname(__DIR__, 2), $helpFileName)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Maker/Common/CanGenerateTestsTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Maker\Common; 13 | 14 | use Symfony\Bundle\MakerBundle\ConsoleStyle; 15 | use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; 16 | use Symfony\Component\Console\Command\Command; 17 | use Symfony\Component\Console\Input\InputInterface; 18 | use Symfony\Component\Console\Input\InputOption; 19 | 20 | /** 21 | * @author Jesse Rushlow 22 | * 23 | * @internal 24 | */ 25 | trait CanGenerateTestsTrait 26 | { 27 | private bool $generateTests = false; 28 | 29 | public function configureCommandWithTestsOption(Command $command): Command 30 | { 31 | $testsHelp = file_get_contents(\dirname(__DIR__, 3).'/config/help/_WithTests.txt'); 32 | $help = $command->getHelp()."\n".$testsHelp; 33 | 34 | $command 35 | ->addOption(name: 'with-tests', mode: InputOption::VALUE_NONE, description: 'Generate PHPUnit Tests') 36 | ->setHelp($help) 37 | ; 38 | 39 | return $command; 40 | } 41 | 42 | public function interactSetGenerateTests(InputInterface $input, ConsoleStyle $io): void 43 | { 44 | // Sanity check for maker dev's - End user should never see this. 45 | if (!$input->hasOption('with-tests')) { 46 | throw new RuntimeCommandException('Whoops! "--with-tests" option does not exist. Call "addWithTestsOptions()" in the makers "configureCommand().'); 47 | } 48 | 49 | $this->generateTests = $input->getOption('with-tests'); 50 | 51 | if (!$this->generateTests) { 52 | $this->generateTests = $io->confirm('Do you want to generate PHPUnit tests? [Experimental]', false); 53 | } 54 | } 55 | 56 | public function shouldGenerateTests(): bool 57 | { 58 | return $this->generateTests; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Maker/Common/EntityIdTypeEnum.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Maker\Common; 13 | 14 | /** 15 | * @author Jesse Rushlow 16 | * 17 | * @internal 18 | */ 19 | enum EntityIdTypeEnum 20 | { 21 | case INT; 22 | case UUID; 23 | case ULID; 24 | } 25 | -------------------------------------------------------------------------------- /src/Maker/Common/InstallDependencyTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Maker\Common; 13 | 14 | use Symfony\Bundle\MakerBundle\ConsoleStyle; 15 | use Symfony\Component\Process\Process; 16 | 17 | /** 18 | * @author Jesse Rushlow 19 | * 20 | * @internal 21 | */ 22 | trait InstallDependencyTrait 23 | { 24 | /** 25 | * @param string $composerPackage Fully qualified composer package to install e.g. symfony/maker-bundle 26 | */ 27 | public function installDependencyIfNeeded(ConsoleStyle $io, string $expectedClassToExist, string $composerPackage): ConsoleStyle 28 | { 29 | if (class_exists($expectedClassToExist)) { 30 | return $io; 31 | } 32 | 33 | $io->writeln(\sprintf('Running: composer require %s', $composerPackage)); 34 | 35 | Process::fromShellCommandline(\sprintf('composer require %s', $composerPackage))->run(); 36 | 37 | $io->writeln(\sprintf('%s successfully installed!', $composerPackage)); 38 | $io->newLine(); 39 | 40 | return $io; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Maker/Common/UidTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Maker\Common; 13 | 14 | use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; 15 | use Symfony\Component\Console\Command\Command; 16 | use Symfony\Component\Console\Input\InputInterface; 17 | use Symfony\Component\Console\Input\InputOption; 18 | use Symfony\Component\Uid\Ulid; 19 | use Symfony\Component\Uid\Uuid; 20 | 21 | /** 22 | * @author Jesse Rushlow 23 | * 24 | * @internal 25 | */ 26 | trait UidTrait 27 | { 28 | private bool $usesUuid = false; 29 | private bool $usesUlid = false; 30 | 31 | /** 32 | * Call this in a maker's configure() to consistently allow entity's with UUID's. 33 | * This should be called after you calling "setHelp()" in the maker. 34 | */ 35 | protected function addWithUuidOption(Command $command): Command 36 | { 37 | $uidHelp = file_get_contents(\dirname(__DIR__, 3).'/config/help/_WithUid.txt'); 38 | $help = $command->getHelp()."\n".$uidHelp; 39 | 40 | $command 41 | ->addOption(name: 'with-uuid', mode: InputOption::VALUE_NONE, description: 'Use UUID for entity "id"') 42 | ->addOption('with-ulid', mode: InputOption::VALUE_NONE, description: 'Use ULID for entity "id"') 43 | ->setHelp($help) 44 | ; 45 | 46 | return $command; 47 | } 48 | 49 | /** 50 | * Call this as early as possible in a maker's interact(). 51 | */ 52 | protected function checkIsUsingUid(InputInterface $input): void 53 | { 54 | if (($this->usesUuid = $input->getOption('with-uuid')) && !class_exists(Uuid::class)) { 55 | throw new RuntimeCommandException('You must install symfony/uid to use Uuid\'s as "id" (composer require symfony/uid)'); 56 | } 57 | 58 | if (($this->usesUlid = $input->getOption('with-ulid')) && !class_exists(Ulid::class)) { 59 | throw new RuntimeCommandException('You must install symfony/uid to use Ulid\'s as "id" (composer require symfony/uid)'); 60 | } 61 | 62 | if ($this->usesUuid && $this->usesUlid) { 63 | throw new RuntimeCommandException('Setting --with-uuid & --with-ulid at the same time is not allowed. Please choose only one.'); 64 | } 65 | } 66 | 67 | protected function getIdType(): EntityIdTypeEnum 68 | { 69 | if ($this->usesUuid) { 70 | return EntityIdTypeEnum::UUID; 71 | } 72 | 73 | if ($this->usesUlid) { 74 | return EntityIdTypeEnum::ULID; 75 | } 76 | 77 | return EntityIdTypeEnum::INT; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Maker/MakeFixtures.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Maker; 13 | 14 | use Doctrine\Bundle\FixturesBundle\Fixture; 15 | use Doctrine\ORM\Mapping\Column; 16 | use Doctrine\Persistence\ObjectManager; 17 | use Symfony\Bundle\MakerBundle\ConsoleStyle; 18 | use Symfony\Bundle\MakerBundle\DependencyBuilder; 19 | use Symfony\Bundle\MakerBundle\Generator; 20 | use Symfony\Bundle\MakerBundle\InputConfiguration; 21 | use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; 22 | use Symfony\Component\Console\Command\Command; 23 | use Symfony\Component\Console\Input\InputArgument; 24 | use Symfony\Component\Console\Input\InputInterface; 25 | 26 | /** 27 | * @author Javier Eguiluz 28 | * @author Ryan Weaver 29 | */ 30 | final class MakeFixtures extends AbstractMaker 31 | { 32 | public static function getCommandName(): string 33 | { 34 | return 'make:fixtures'; 35 | } 36 | 37 | public static function getCommandDescription(): string 38 | { 39 | return 'Create a new class to load Doctrine fixtures'; 40 | } 41 | 42 | /** @return void */ 43 | public function configureCommand(Command $command, InputConfiguration $inputConf) 44 | { 45 | $command 46 | ->addArgument('fixtures-class', InputArgument::OPTIONAL, 'The class name of the fixtures to create (e.g. AppFixtures)') 47 | ->setHelp($this->getHelpFileContents('MakeFixture.txt')) 48 | ; 49 | } 50 | 51 | /** @return void */ 52 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) 53 | { 54 | $fixturesClassNameDetails = $generator->createClassNameDetails( 55 | $input->getArgument('fixtures-class'), 56 | 'DataFixtures\\' 57 | ); 58 | 59 | $useStatements = new UseStatementGenerator([ 60 | Fixture::class, 61 | ObjectManager::class, 62 | ]); 63 | 64 | $generator->generateClass( 65 | $fixturesClassNameDetails->getFullName(), 66 | 'doctrine/Fixtures.tpl.php', 67 | [ 68 | 'use_statements' => $useStatements, 69 | ] 70 | ); 71 | 72 | $generator->writeChanges(); 73 | 74 | $this->writeSuccessMessage($io); 75 | 76 | $io->text([ 77 | 'Next: Open your new fixtures class and start customizing it.', 78 | \sprintf('Load your fixtures by running: php %s doctrine:fixtures:load', $_SERVER['PHP_SELF']), 79 | 'Docs: https://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html', 80 | ]); 81 | } 82 | 83 | /** @return void */ 84 | public function configureDependencies(DependencyBuilder $dependencies) 85 | { 86 | $dependencies->addClassDependency( 87 | Column::class, 88 | 'doctrine' 89 | ); 90 | $dependencies->addClassDependency( 91 | Fixture::class, 92 | 'orm-fixtures', 93 | true, 94 | true 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Maker/MakeFunctionalTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Maker; 13 | 14 | use Symfony\Bundle\FrameworkBundle\Test\WebTestAssertionsTrait; 15 | use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; 16 | use Symfony\Bundle\MakerBundle\ConsoleStyle; 17 | use Symfony\Bundle\MakerBundle\DependencyBuilder; 18 | use Symfony\Bundle\MakerBundle\Generator; 19 | use Symfony\Bundle\MakerBundle\InputConfiguration; 20 | use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; 21 | use Symfony\Component\BrowserKit\History; 22 | use Symfony\Component\Console\Command\Command; 23 | use Symfony\Component\Console\Input\InputArgument; 24 | use Symfony\Component\Console\Input\InputInterface; 25 | use Symfony\Component\CssSelector\CssSelectorConverter; 26 | use Symfony\Component\Panther\PantherTestCase; 27 | use Symfony\Component\Panther\PantherTestCaseTrait; 28 | 29 | trigger_deprecation('symfony/maker-bundle', '1.29', 'The "%s" class is deprecated, use "%s" instead.', MakeFunctionalTest::class, MakeTest::class); 30 | 31 | /** 32 | * @deprecated since MakerBundle 1.29, use Symfony\Bundle\MakerBundle\Maker\MakeTest instead. 33 | * 34 | * @author Javier Eguiluz 35 | * @author Ryan Weaver 36 | */ 37 | class MakeFunctionalTest extends AbstractMaker 38 | { 39 | public static function getCommandName(): string 40 | { 41 | return 'make:functional-test'; 42 | } 43 | 44 | public static function getCommandDescription(): string 45 | { 46 | return 'Create a new functional test class'; 47 | } 48 | 49 | public function configureCommand(Command $command, InputConfiguration $inputConfig): void 50 | { 51 | $command 52 | ->addArgument('name', InputArgument::OPTIONAL, 'The name of the functional test class (e.g. DefaultControllerTest)') 53 | ->setHelp($this->getHelpFileContents('MakeFunctionalTest.txt')) 54 | ; 55 | } 56 | 57 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void 58 | { 59 | $testClassNameDetails = $generator->createClassNameDetails( 60 | $input->getArgument('name'), 61 | 'Tests\\', 62 | 'Test' 63 | ); 64 | 65 | $pantherAvailable = trait_exists(PantherTestCaseTrait::class); 66 | 67 | $useStatements = new UseStatementGenerator([ 68 | $pantherAvailable ? PantherTestCase::class : WebTestCase::class, 69 | ]); 70 | 71 | $generator->generateClass( 72 | $testClassNameDetails->getFullName(), 73 | 'test/Functional.tpl.php', 74 | [ 75 | 'use_statements' => $useStatements, 76 | 'web_assertions_are_available' => trait_exists(WebTestAssertionsTrait::class), 77 | 'panther_is_available' => $pantherAvailable, 78 | ] 79 | ); 80 | 81 | $generator->writeChanges(); 82 | 83 | $this->writeSuccessMessage($io); 84 | 85 | $io->text([ 86 | 'Next: Open your new test class and start customizing it.', 87 | 'Find the documentation at https://symfony.com/doc/current/testing.html#functional-tests', 88 | ]); 89 | } 90 | 91 | public function configureDependencies(DependencyBuilder $dependencies): void 92 | { 93 | $dependencies->addClassDependency( 94 | History::class, 95 | 'browser-kit', 96 | true, 97 | true 98 | ); 99 | $dependencies->addClassDependency( 100 | CssSelectorConverter::class, 101 | 'css-selector', 102 | true, 103 | true 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Maker/MakeMessengerMiddleware.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Maker; 13 | 14 | use Symfony\Bundle\MakerBundle\ConsoleStyle; 15 | use Symfony\Bundle\MakerBundle\DependencyBuilder; 16 | use Symfony\Bundle\MakerBundle\Generator; 17 | use Symfony\Bundle\MakerBundle\InputConfiguration; 18 | use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; 19 | use Symfony\Component\Console\Command\Command; 20 | use Symfony\Component\Console\Input\InputArgument; 21 | use Symfony\Component\Console\Input\InputInterface; 22 | use Symfony\Component\Messenger\Envelope; 23 | use Symfony\Component\Messenger\MessageBusInterface; 24 | use Symfony\Component\Messenger\Middleware\MiddlewareInterface; 25 | use Symfony\Component\Messenger\Middleware\StackInterface; 26 | 27 | /** 28 | * @author Imad ZAIRIG 29 | * 30 | * @internal 31 | */ 32 | final class MakeMessengerMiddleware extends AbstractMaker 33 | { 34 | public static function getCommandName(): string 35 | { 36 | return 'make:messenger-middleware'; 37 | } 38 | 39 | public static function getCommandDescription(): string 40 | { 41 | return 'Create a new messenger middleware'; 42 | } 43 | 44 | public function configureCommand(Command $command, InputConfiguration $inputConfig): void 45 | { 46 | $command 47 | ->addArgument('name', InputArgument::OPTIONAL, 'The name of the middleware class (e.g. CustomMiddleware)') 48 | ->setHelp($this->getHelpFileContents('MakeMessage.txt')) 49 | ; 50 | } 51 | 52 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void 53 | { 54 | $middlewareClassNameDetails = $generator->createClassNameDetails( 55 | $input->getArgument('name'), 56 | 'Middleware\\', 57 | 'Middleware' 58 | ); 59 | 60 | $useStatements = new UseStatementGenerator([ 61 | Envelope::class, 62 | MiddlewareInterface::class, 63 | StackInterface::class, 64 | ]); 65 | 66 | $generator->generateClass( 67 | $middlewareClassNameDetails->getFullName(), 68 | 'middleware/Middleware.tpl.php', 69 | [ 70 | 'use_statements' => $useStatements, 71 | ] 72 | ); 73 | 74 | $generator->writeChanges(); 75 | 76 | $this->writeSuccessMessage($io); 77 | 78 | $io->text([ 79 | 'Next:', 80 | \sprintf('- Open the %s class and add the code you need', $middlewareClassNameDetails->getFullName()), 81 | '- Add the middleware to your config/packages/messenger.yaml file', 82 | 'Find the documentation at https://symfony.com/doc/current/messenger.html#middleware', 83 | ]); 84 | } 85 | 86 | public function configureDependencies(DependencyBuilder $dependencies): void 87 | { 88 | $dependencies->addClassDependency( 89 | MessageBusInterface::class, 90 | 'messenger' 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Maker/MakeSerializerEncoder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Maker; 13 | 14 | use Symfony\Bundle\MakerBundle\ConsoleStyle; 15 | use Symfony\Bundle\MakerBundle\DependencyBuilder; 16 | use Symfony\Bundle\MakerBundle\Generator; 17 | use Symfony\Bundle\MakerBundle\InputConfiguration; 18 | use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; 19 | use Symfony\Component\Console\Command\Command; 20 | use Symfony\Component\Console\Input\InputArgument; 21 | use Symfony\Component\Console\Input\InputInterface; 22 | use Symfony\Component\HttpKernel\Kernel; 23 | use Symfony\Component\Serializer\Encoder\DecoderInterface; 24 | use Symfony\Component\Serializer\Encoder\EncoderInterface; 25 | use Symfony\Component\Serializer\Serializer; 26 | 27 | /** 28 | * @author Piotr Grabski-Gradzinski 29 | */ 30 | final class MakeSerializerEncoder extends AbstractMaker 31 | { 32 | public static function getCommandName(): string 33 | { 34 | return 'make:serializer:encoder'; 35 | } 36 | 37 | public static function getCommandDescription(): string 38 | { 39 | return 'Create a new serializer encoder class'; 40 | } 41 | 42 | public function configureCommand(Command $command, InputConfiguration $inputConfig): void 43 | { 44 | $command 45 | ->addArgument('name', InputArgument::OPTIONAL, 'Choose a class name for your encoder (e.g. YamlEncoder)') 46 | ->addArgument('format', InputArgument::OPTIONAL, 'Pick your format name (e.g. yaml)') 47 | ->setHelp($this->getHelpFileContents('MakeSerializerEncoder.txt')) 48 | ; 49 | } 50 | 51 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void 52 | { 53 | $encoderClassNameDetails = $generator->createClassNameDetails( 54 | $input->getArgument('name'), 55 | 'Serializer\\', 56 | 'Encoder' 57 | ); 58 | $format = $input->getArgument('format'); 59 | 60 | $useStatements = new UseStatementGenerator([ 61 | DecoderInterface::class, 62 | EncoderInterface::class, 63 | ]); 64 | 65 | /* @legacy - Remove "decoder_return_type" when Symfony 6.4 is no longer supported */ 66 | $generator->generateClass( 67 | $encoderClassNameDetails->getFullName(), 68 | 'serializer/Encoder.tpl.php', 69 | [ 70 | 'use_statements' => $useStatements, 71 | 'format' => $format, 72 | 'use_decoder_return_type' => Kernel::VERSION_ID >= 70000, 73 | ] 74 | ); 75 | 76 | $generator->writeChanges(); 77 | 78 | $this->writeSuccessMessage($io); 79 | 80 | $io->text([ 81 | 'Next: Open your new serializer encoder class and start customizing it.', 82 | 'Find the documentation at http://symfony.com/doc/current/serializer/custom_encoders.html', 83 | ]); 84 | } 85 | 86 | public function configureDependencies(DependencyBuilder $dependencies): void 87 | { 88 | $dependencies->addClassDependency( 89 | Serializer::class, 90 | 'serializer' 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Maker/MakeTwigExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Maker; 13 | 14 | use Symfony\Bundle\MakerBundle\ConsoleStyle; 15 | use Symfony\Bundle\MakerBundle\DependencyBuilder; 16 | use Symfony\Bundle\MakerBundle\Generator; 17 | use Symfony\Bundle\MakerBundle\InputConfiguration; 18 | use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; 19 | use Symfony\Component\Console\Command\Command; 20 | use Symfony\Component\Console\Input\InputArgument; 21 | use Symfony\Component\Console\Input\InputInterface; 22 | use Twig\Extension\AbstractExtension; 23 | use Twig\Extension\RuntimeExtensionInterface; 24 | use Twig\TwigFilter; 25 | use Twig\TwigFunction; 26 | 27 | /** 28 | * @author Javier Eguiluz 29 | * @author Ryan Weaver 30 | */ 31 | final class MakeTwigExtension extends AbstractMaker 32 | { 33 | public static function getCommandName(): string 34 | { 35 | return 'make:twig-extension'; 36 | } 37 | 38 | public static function getCommandDescription(): string 39 | { 40 | return 'Create a new Twig extension with its runtime class'; 41 | } 42 | 43 | public function configureCommand(Command $command, InputConfiguration $inputConfig): void 44 | { 45 | $command 46 | ->addArgument('name', InputArgument::OPTIONAL, 'The name of the Twig extension class (e.g. AppExtension)') 47 | ->setHelp($this->getHelpFileContents('MakeTwigExtension.txt')) 48 | ; 49 | } 50 | 51 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void 52 | { 53 | $name = $input->getArgument('name'); 54 | 55 | $extensionClassNameDetails = $generator->createClassNameDetails( 56 | $name, 57 | 'Twig\\Extension\\', 58 | 'Extension' 59 | ); 60 | 61 | $runtimeClassNameDetails = $generator->createClassNameDetails( 62 | $name, 63 | 'Twig\\Runtime\\', 64 | 'Runtime' 65 | ); 66 | 67 | $useStatements = new UseStatementGenerator([ 68 | AbstractExtension::class, 69 | TwigFilter::class, 70 | TwigFunction::class, 71 | $runtimeClassNameDetails->getFullName(), 72 | ]); 73 | 74 | $runtimeUseStatements = new UseStatementGenerator([ 75 | RuntimeExtensionInterface::class, 76 | ]); 77 | 78 | $generator->generateClass( 79 | $extensionClassNameDetails->getFullName(), 80 | 'twig/Extension.tpl.php', 81 | ['use_statements' => $useStatements, 'runtime_class_name' => $runtimeClassNameDetails->getShortName()] 82 | ); 83 | 84 | $generator->generateClass( 85 | $runtimeClassNameDetails->getFullName(), 86 | 'twig/Runtime.tpl.php', 87 | ['use_statements' => $runtimeUseStatements] 88 | ); 89 | 90 | $generator->writeChanges(); 91 | 92 | $this->writeSuccessMessage($io); 93 | 94 | $io->text([ 95 | 'Next: Open your new extension class and start customizing it.', 96 | 'Find the documentation at http://symfony.com/doc/current/templating/twig_extension.html', 97 | ]); 98 | } 99 | 100 | public function configureDependencies(DependencyBuilder $dependencies): void 101 | { 102 | $dependencies->addClassDependency( 103 | AbstractExtension::class, 104 | 'twig' 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Maker/MakeUnitTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Maker; 13 | 14 | use PHPUnit\Framework\TestCase; 15 | use Symfony\Bundle\MakerBundle\ConsoleStyle; 16 | use Symfony\Bundle\MakerBundle\DependencyBuilder; 17 | use Symfony\Bundle\MakerBundle\Generator; 18 | use Symfony\Bundle\MakerBundle\InputConfiguration; 19 | use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; 20 | use Symfony\Component\Console\Command\Command; 21 | use Symfony\Component\Console\Input\InputArgument; 22 | use Symfony\Component\Console\Input\InputInterface; 23 | 24 | trigger_deprecation('symfony/maker-bundle', '1.29', 'The "%s" class is deprecated, use "%s" instead.', MakeUnitTest::class, MakeTest::class); 25 | 26 | /** 27 | * @deprecated since MakerBundle 1.29, use Symfony\Bundle\MakerBundle\Maker\MakeTest instead. 28 | * 29 | * @author Javier Eguiluz 30 | * @author Ryan Weaver 31 | */ 32 | final class MakeUnitTest extends AbstractMaker 33 | { 34 | public static function getCommandName(): string 35 | { 36 | return 'make:unit-test'; 37 | } 38 | 39 | public static function getCommandDescription(): string 40 | { 41 | return 'Create a new unit test class'; 42 | } 43 | 44 | public function configureCommand(Command $command, InputConfiguration $inputConfig): void 45 | { 46 | $command 47 | ->addArgument('name', InputArgument::OPTIONAL, 'The name of the unit test class (e.g. UtilTest)') 48 | ->setHelp($this->getHelpFileContents('MakeUnitTest.txt')) 49 | ; 50 | } 51 | 52 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void 53 | { 54 | $testClassNameDetails = $generator->createClassNameDetails( 55 | $input->getArgument('name'), 56 | 'Tests\\', 57 | 'Test' 58 | ); 59 | 60 | $generator->generateClass( 61 | $testClassNameDetails->getFullName(), 62 | 'test/Unit.tpl.php', 63 | ['use_statements' => new UseStatementGenerator([TestCase::class])] 64 | ); 65 | 66 | $generator->writeChanges(); 67 | 68 | $this->writeSuccessMessage($io); 69 | 70 | $io->text([ 71 | 'Next: Open your new test class and start customizing it.', 72 | 'Find the documentation at https://symfony.com/doc/current/testing.html#unit-tests', 73 | ]); 74 | } 75 | 76 | public function configureDependencies(DependencyBuilder $dependencies): void 77 | { 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Maker/MakeValidator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Maker; 13 | 14 | use Symfony\Bundle\MakerBundle\ConsoleStyle; 15 | use Symfony\Bundle\MakerBundle\DependencyBuilder; 16 | use Symfony\Bundle\MakerBundle\Generator; 17 | use Symfony\Bundle\MakerBundle\InputConfiguration; 18 | use Symfony\Bundle\MakerBundle\Str; 19 | use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData; 20 | use Symfony\Component\Console\Command\Command; 21 | use Symfony\Component\Console\Input\InputArgument; 22 | use Symfony\Component\Console\Input\InputInterface; 23 | use Symfony\Component\Validator\Constraint; 24 | use Symfony\Component\Validator\ConstraintValidator; 25 | use Symfony\Component\Validator\Validation; 26 | 27 | /** 28 | * @author Javier Eguiluz 29 | * @author Ryan Weaver 30 | */ 31 | final class MakeValidator extends AbstractMaker 32 | { 33 | public static function getCommandName(): string 34 | { 35 | return 'make:validator'; 36 | } 37 | 38 | public static function getCommandDescription(): string 39 | { 40 | return 'Create a new validator and constraint class'; 41 | } 42 | 43 | /** @return void */ 44 | public function configureCommand(Command $command, InputConfiguration $inputConf) 45 | { 46 | $command 47 | ->addArgument('name', InputArgument::OPTIONAL, 'The name of the validator class (e.g. EnabledValidator)') 48 | ->setHelp($this->getHelpFileContents('MakeValidator.txt')) 49 | ; 50 | } 51 | 52 | /** @return void */ 53 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) 54 | { 55 | $validatorClassData = ClassData::create( 56 | class: \sprintf('Validator\\%s', $input->getArgument('name')), 57 | suffix: 'Validator', 58 | extendsClass: ConstraintValidator::class, 59 | useStatements: [ 60 | Constraint::class, 61 | ], 62 | ); 63 | 64 | $constraintDataClass = ClassData::create( 65 | class: \sprintf('Validator\\%s', Str::removeSuffix($validatorClassData->getClassName(), 'Validator')), 66 | extendsClass: Constraint::class, 67 | ); 68 | 69 | $generator->generateClassFromClassData( 70 | $validatorClassData, 71 | 'validator/Validator.tpl.php', 72 | [ 73 | 'constraint_class_name' => $constraintDataClass->getClassName(), 74 | ] 75 | ); 76 | 77 | $generator->generateClassFromClassData( 78 | $constraintDataClass, 79 | 'validator/Constraint.tpl.php', 80 | ); 81 | 82 | $generator->writeChanges(); 83 | 84 | $this->writeSuccessMessage($io); 85 | 86 | $io->text([ 87 | 'Next: Open your new constraint & validators and add your logic.', 88 | 'Find the documentation at http://symfony.com/doc/current/validation/custom_constraint.html', 89 | ]); 90 | } 91 | 92 | /** @return void */ 93 | public function configureDependencies(DependencyBuilder $dependencies) 94 | { 95 | $dependencies->addClassDependency( 96 | Validation::class, 97 | 'validator' 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Maker/MakeVoter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Maker; 13 | 14 | use Symfony\Bundle\MakerBundle\ConsoleStyle; 15 | use Symfony\Bundle\MakerBundle\DependencyBuilder; 16 | use Symfony\Bundle\MakerBundle\Generator; 17 | use Symfony\Bundle\MakerBundle\InputConfiguration; 18 | use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData; 19 | use Symfony\Component\Console\Command\Command; 20 | use Symfony\Component\Console\Input\InputArgument; 21 | use Symfony\Component\Console\Input\InputInterface; 22 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 23 | use Symfony\Component\Security\Core\Authorization\Voter\Voter; 24 | use Symfony\Component\Security\Core\User\UserInterface; 25 | 26 | /** 27 | * @author Javier Eguiluz 28 | * @author Ryan Weaver 29 | */ 30 | final class MakeVoter extends AbstractMaker 31 | { 32 | public static function getCommandName(): string 33 | { 34 | return 'make:voter'; 35 | } 36 | 37 | public static function getCommandDescription(): string 38 | { 39 | return 'Create a new security voter class'; 40 | } 41 | 42 | public function configureCommand(Command $command, InputConfiguration $inputConfig): void 43 | { 44 | $command 45 | ->addArgument('name', InputArgument::OPTIONAL, 'The name of the security voter class (e.g. BlogPostVoter)') 46 | ->setHelp($this->getHelpFileContents('MakeVoter.txt')) 47 | ; 48 | } 49 | 50 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void 51 | { 52 | $voterClassData = ClassData::create( 53 | class: \sprintf('Security\Voter\%s', $input->getArgument('name')), 54 | suffix: 'Voter', 55 | extendsClass: Voter::class, 56 | useStatements: [ 57 | TokenInterface::class, 58 | Voter::class, 59 | UserInterface::class, 60 | ] 61 | ); 62 | 63 | $generator->generateClassFromClassData( 64 | $voterClassData, 65 | 'security/Voter.tpl.php', 66 | ); 67 | 68 | $generator->writeChanges(); 69 | 70 | $this->writeSuccessMessage($io); 71 | 72 | $io->text([ 73 | 'Next: Open your voter and add your logic.', 74 | 'Find the documentation at https://symfony.com/doc/current/security/voters.html', 75 | ]); 76 | } 77 | 78 | public function configureDependencies(DependencyBuilder $dependencies): void 79 | { 80 | $dependencies->addClassDependency( 81 | Voter::class, 82 | 'security' 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/MakerBundle.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle; 13 | 14 | use Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass\MakeCommandRegistrationPass; 15 | use Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass\RemoveMissingParametersPass; 16 | use Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass\SetDoctrineAnnotatedPrefixesPass; 17 | use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator; 18 | use Symfony\Component\DependencyInjection\Compiler\PassConfig; 19 | use Symfony\Component\DependencyInjection\ContainerBuilder; 20 | use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; 21 | use Symfony\Component\HttpKernel\Bundle\AbstractBundle; 22 | 23 | /** 24 | * @author Javier Eguiluz 25 | * @author Ryan Weaver 26 | */ 27 | class MakerBundle extends AbstractBundle 28 | { 29 | protected string $extensionAlias = 'maker'; 30 | 31 | public function configure(DefinitionConfigurator $definition): void 32 | { 33 | $definition->rootNode() 34 | ->children() 35 | ->scalarNode('root_namespace')->defaultValue('App')->end() 36 | ->booleanNode('generate_final_classes')->defaultTrue()->end() 37 | ->booleanNode('generate_final_entities')->defaultFalse()->end() 38 | ->end() 39 | ; 40 | } 41 | 42 | public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void 43 | { 44 | $container->import('../config/services.xml'); 45 | $container->import('../config/makers.xml'); 46 | 47 | $rootNamespace = trim($config['root_namespace'], '\\'); 48 | 49 | $container->services() 50 | ->get('maker.autoloader_finder') 51 | ->arg(0, $rootNamespace) 52 | ->get('maker.generator') 53 | ->arg(1, $rootNamespace) 54 | ->get('maker.doctrine_helper') 55 | ->arg(0, \sprintf('%s\\Entity', $rootNamespace)) 56 | ->get('maker.template_component_generator') 57 | ->arg(0, $config['generate_final_classes']) 58 | ->arg(1, $config['generate_final_entities']) 59 | ->arg(2, $rootNamespace) 60 | ; 61 | 62 | $builder 63 | ->registerForAutoconfiguration(MakerInterface::class) 64 | ->addTag(MakeCommandRegistrationPass::MAKER_TAG) 65 | ; 66 | } 67 | 68 | public function build(ContainerBuilder $container): void 69 | { 70 | // add a priority so we run before the core command pass 71 | $container->addCompilerPass(new MakeCommandRegistrationPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 10); 72 | $container->addCompilerPass(new RemoveMissingParametersPass()); 73 | $container->addCompilerPass(new SetDoctrineAnnotatedPrefixesPass()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/MakerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle; 13 | 14 | use Symfony\Component\Console\Command\Command; 15 | use Symfony\Component\Console\Input\InputInterface; 16 | 17 | /** 18 | * Interface that all maker commands must implement. 19 | * 20 | * @method static string getCommandDescription() 21 | * 22 | * @author Ryan Weaver 23 | */ 24 | interface MakerInterface 25 | { 26 | /** 27 | * Return the command name for your maker (e.g. make:report). 28 | */ 29 | public static function getCommandName(): string; 30 | 31 | /** 32 | * Configure the command: set description, input arguments, options, etc. 33 | * 34 | * By default, all arguments will be asked interactively. If you want 35 | * to avoid that, use the $inputConfig->setArgumentAsNonInteractive() method. 36 | */ 37 | public function configureCommand(Command $command, InputConfiguration $inputConfig); 38 | 39 | /** 40 | * Configure any library dependencies that your maker requires. 41 | */ 42 | public function configureDependencies(DependencyBuilder $dependencies); 43 | 44 | /** 45 | * If necessary, you can use this method to interactively ask the user for input. 46 | */ 47 | public function interact(InputInterface $input, ConsoleStyle $io, Command $command); 48 | 49 | /** 50 | * Called after normal code generation: allows you to do anything. 51 | */ 52 | public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator); 53 | } 54 | -------------------------------------------------------------------------------- /src/Renderer/FormTypeRenderer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Renderer; 13 | 14 | use Symfony\Bundle\MakerBundle\Generator; 15 | use Symfony\Bundle\MakerBundle\Str; 16 | use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; 17 | use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; 18 | use Symfony\Component\Form\AbstractType; 19 | use Symfony\Component\Form\FormBuilderInterface; 20 | use Symfony\Component\OptionsResolver\OptionsResolver; 21 | 22 | /** 23 | * @internal 24 | */ 25 | final class FormTypeRenderer 26 | { 27 | public function __construct( 28 | private Generator $generator, 29 | ) { 30 | } 31 | 32 | public function render(ClassNameDetails $formClassDetails, array $formFields, ?ClassNameDetails $boundClassDetails = null, array $constraintClasses = [], array $extraUseClasses = []): void 33 | { 34 | $fieldTypeUseStatements = []; 35 | $fields = []; 36 | foreach ($formFields as $name => $fieldTypeOptions) { 37 | $fieldTypeOptions ??= ['type' => null, 'options_code' => null]; 38 | 39 | if (isset($fieldTypeOptions['type'])) { 40 | $fieldTypeUseStatements[] = $fieldTypeOptions['type']; 41 | $fieldTypeOptions['type'] = Str::getShortClassName($fieldTypeOptions['type']); 42 | if (\array_key_exists('extra_use_classes', $fieldTypeOptions) && \count($fieldTypeOptions['extra_use_classes']) > 0) { 43 | $extraUseClasses = array_merge($extraUseClasses, $fieldTypeOptions['extra_use_classes'] ?? []); 44 | $fieldTypeOptions['options_code'] = str_replace( 45 | $fieldTypeOptions['extra_use_classes'], 46 | array_map(fn ($class) => Str::getShortClassName($class), $fieldTypeOptions['extra_use_classes']), 47 | $fieldTypeOptions['options_code'] 48 | ); 49 | } 50 | } 51 | 52 | $fields[$name] = $fieldTypeOptions; 53 | } 54 | 55 | $useStatements = new UseStatementGenerator(array_unique(array_merge( 56 | $fieldTypeUseStatements, 57 | $extraUseClasses, 58 | $constraintClasses 59 | ))); 60 | 61 | $useStatements->addUseStatement([ 62 | AbstractType::class, 63 | FormBuilderInterface::class, 64 | OptionsResolver::class, 65 | ]); 66 | 67 | if ($boundClassDetails) { 68 | $useStatements->addUseStatement($boundClassDetails->getFullName()); 69 | } 70 | 71 | $this->generator->generateClass( 72 | $formClassDetails->getFullName(), 73 | 'form/Form.tpl.php', 74 | [ 75 | 'use_statements' => $useStatements, 76 | 'bounded_class_name' => $boundClassDetails ? $boundClassDetails->getShortName() : null, 77 | 'form_fields' => $fields, 78 | ] 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Resources/bin/php-cs-fixer-v3.49.0.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symfony/maker-bundle/81eb8d9df6d1f434d8562cdce76167bc93dd2645/src/Resources/bin/php-cs-fixer-v3.49.0.phar -------------------------------------------------------------------------------- /src/Security/Model/Authenticator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Security\Model; 13 | 14 | /** 15 | * @author Jesse Rushlow 16 | * 17 | * @internal 18 | */ 19 | final class Authenticator 20 | { 21 | public function __construct( 22 | public AuthenticatorType $type, 23 | public string $firewallName, 24 | public ?string $authenticatorClass = null, 25 | ) { 26 | } 27 | 28 | /** 29 | * Useful for asking questions like "Which authenticator do you want to use?". 30 | */ 31 | public function __toString(): string 32 | { 33 | return \sprintf( 34 | '"%s" in the "%s" firewall', 35 | $this->authenticatorClass ?? $this->type->value, 36 | $this->firewallName, 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Security/Model/AuthenticatorType.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Security\Model; 13 | 14 | /** 15 | * @author Jesse Rushlow 16 | * 17 | * @internal 18 | */ 19 | enum AuthenticatorType: string 20 | { 21 | case FORM_LOGIN = 'form_login'; 22 | case JSON_LOGIN = 'json_login'; 23 | case HTTP_BASIC = 'http_basic'; 24 | case LOGIN_LINK = 'login_link'; 25 | case ACCESS_TOKEN = 'access_token'; 26 | case X509 = 'x509'; 27 | case REMOTE_USER = 'remote_user'; 28 | 29 | case CUSTOM = 'custom_authenticator'; 30 | } 31 | -------------------------------------------------------------------------------- /src/Security/SecurityControllerBuilder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Security; 13 | 14 | use PhpParser\Builder\Param; 15 | use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; 16 | use Symfony\Component\HttpFoundation\Response; 17 | use Symfony\Component\Routing\Attribute\Route; 18 | use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; 19 | 20 | /** 21 | * @internal 22 | */ 23 | final class SecurityControllerBuilder 24 | { 25 | public function addLoginMethod(ClassSourceManipulator $manipulator): void 26 | { 27 | $loginMethodBuilder = $manipulator->createMethodBuilder('login', 'Response', false); 28 | 29 | $loginMethodBuilder->addAttribute($manipulator->buildAttributeNode(Route::class, ['path' => '/login', 'name' => 'app_login'])); 30 | 31 | $manipulator->addUseStatementIfNecessary(Response::class); 32 | $manipulator->addUseStatementIfNecessary(Route::class); 33 | $manipulator->addUseStatementIfNecessary(AuthenticationUtils::class); 34 | 35 | $loginMethodBuilder->addParam( 36 | (new Param('authenticationUtils'))->setType('AuthenticationUtils') 37 | ); 38 | 39 | $manipulator->addMethodBody($loginMethodBuilder, <<<'CODE' 40 | getUser()) { 42 | // return $this->redirectToRoute('target_path'); 43 | // } 44 | CODE 45 | ); 46 | $loginMethodBuilder->addStmt($manipulator->createMethodLevelBlankLine()); 47 | $manipulator->addMethodBody($loginMethodBuilder, <<<'CODE' 48 | getLastAuthenticationError(); 51 | // last username entered by the user 52 | $lastUsername = $authenticationUtils->getLastUsername(); 53 | CODE 54 | ); 55 | $loginMethodBuilder->addStmt($manipulator->createMethodLevelBlankLine()); 56 | $manipulator->addMethodBody($loginMethodBuilder, <<<'CODE' 57 | render( 59 | 'security/login.html.twig', 60 | [ 61 | 'last_username' => $lastUsername, 62 | 'error' => $error, 63 | ] 64 | ); 65 | CODE 66 | ); 67 | $manipulator->addMethodBuilder($loginMethodBuilder); 68 | } 69 | 70 | public function addLogoutMethod(ClassSourceManipulator $manipulator): void 71 | { 72 | $logoutMethodBuilder = $manipulator->createMethodBuilder('logout', 'void', false); 73 | 74 | $logoutMethodBuilder->addAttribute($manipulator->buildAttributeNode(Route::class, ['path' => '/logout', 'name' => 'app_logout'])); 75 | 76 | $manipulator->addUseStatementIfNecessary(Route::class); 77 | $manipulator->addMethodBody($logoutMethodBuilder, <<<'CODE' 78 | addMethodBuilder($logoutMethodBuilder); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Security/UserClassConfiguration.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Security; 13 | 14 | /** 15 | * Configuration about the user's new User class. 16 | * 17 | * @internal 18 | */ 19 | final class UserClassConfiguration 20 | { 21 | private string $userProviderClass; 22 | 23 | public function __construct( 24 | private bool $isEntity, 25 | private string $identityPropertyName, 26 | private bool $hasPassword, 27 | ) { 28 | } 29 | 30 | public function isEntity(): bool 31 | { 32 | return $this->isEntity; 33 | } 34 | 35 | public function getIdentityPropertyName(): string 36 | { 37 | return $this->identityPropertyName; 38 | } 39 | 40 | public function hasPassword(): bool 41 | { 42 | return $this->hasPassword; 43 | } 44 | 45 | public function getUserProviderClass(): string 46 | { 47 | return $this->userProviderClass; 48 | } 49 | 50 | public function setUserProviderClass(string $userProviderClass): void 51 | { 52 | if ($this->isEntity()) { 53 | throw new \LogicException('No custom user class allowed for entity user.'); 54 | } 55 | 56 | $this->userProviderClass = $userProviderClass; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Test/MakerTestKernel.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Test; 13 | 14 | use Symfony\Bundle\FrameworkBundle\FrameworkBundle; 15 | use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; 16 | use Symfony\Bundle\MakerBundle\DependencyInjection\CompilerPass\MakeCommandRegistrationPass; 17 | use Symfony\Bundle\MakerBundle\MakerBundle; 18 | use Symfony\Component\Config\Loader\LoaderInterface; 19 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 20 | use Symfony\Component\DependencyInjection\ContainerBuilder; 21 | use Symfony\Component\HttpKernel\Kernel; 22 | use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; 23 | 24 | class MakerTestKernel extends Kernel implements CompilerPassInterface 25 | { 26 | use MicroKernelTrait; 27 | 28 | private string $testRootDir; 29 | 30 | public function __construct(string $environment, bool $debug) 31 | { 32 | $this->testRootDir = sys_get_temp_dir().'/'.uniqid('sf_maker_', true); 33 | 34 | parent::__construct($environment, $debug); 35 | } 36 | 37 | public function registerBundles(): iterable 38 | { 39 | return [ 40 | new FrameworkBundle(), 41 | new MakerBundle(), 42 | ]; 43 | } 44 | 45 | protected function configureRoutes(RoutingConfigurator $routes) 46 | { 47 | } 48 | 49 | protected function configureRouting(RoutingConfigurator $routes) 50 | { 51 | } 52 | 53 | protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) 54 | { 55 | $c->loadFromExtension('framework', [ 56 | 'secret' => 123, 57 | 'router' => [ 58 | 'utf8' => true, 59 | ], 60 | 'http_method_override' => false, 61 | 'handle_all_throwables' => true, 62 | 'php_errors' => [ 63 | 'log' => true, 64 | ], 65 | ]); 66 | } 67 | 68 | public function getProjectDir(): string 69 | { 70 | return $this->getRootDir(); 71 | } 72 | 73 | public function getRootDir(): string 74 | { 75 | return $this->testRootDir; 76 | } 77 | 78 | /** 79 | * @return void 80 | */ 81 | public function process(ContainerBuilder $container) 82 | { 83 | // makes all makers public to help the tests 84 | foreach ($container->findTaggedServiceIds(MakeCommandRegistrationPass::MAKER_TAG) as $id => $tags) { 85 | $defn = $container->getDefinition($id); 86 | $defn->setPublic(true); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Test/MakerTestProcess.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Test; 13 | 14 | use Symfony\Component\Process\Process; 15 | 16 | /** 17 | * @author Sadicov Vladimir 18 | * 19 | * @internal 20 | */ 21 | final class MakerTestProcess 22 | { 23 | private Process $process; 24 | 25 | private function __construct($commandLine, $cwd, array $envVars, $timeout) 26 | { 27 | $this->process = \is_string($commandLine) 28 | ? Process::fromShellCommandline($commandLine, $cwd, null, null, $timeout) 29 | : new Process($commandLine, $cwd, null, null, $timeout); 30 | 31 | $this->process->setEnv($envVars); 32 | } 33 | 34 | public static function create($commandLine, $cwd, array $envVars = [], $timeout = null): self 35 | { 36 | return new self($commandLine, $cwd, $envVars, $timeout); 37 | } 38 | 39 | public function setInput($input): self 40 | { 41 | $this->process->setInput($input); 42 | 43 | return $this; 44 | } 45 | 46 | public function run($allowToFail = false, array $envVars = []): self 47 | { 48 | if (false !== ($timeout = getenv('MAKER_PROCESS_TIMEOUT'))) { 49 | if ('null' === $timeout) { 50 | $timeout = null; 51 | } 52 | 53 | // Setting a value of null allows for step debugging 54 | $this->process->setTimeout($timeout); 55 | } 56 | 57 | $this->process->run(null, $envVars); 58 | 59 | if (!$allowToFail && !$this->process->isSuccessful()) { 60 | throw new \Exception(\sprintf('Error running command: "%s". Output: "%s". Error: "%s"', $this->process->getCommandLine(), $this->process->getOutput(), $this->process->getErrorOutput())); 61 | } 62 | 63 | return $this; 64 | } 65 | 66 | public function isSuccessful(): bool 67 | { 68 | return $this->process->isSuccessful(); 69 | } 70 | 71 | public function getOutput(): string 72 | { 73 | return $this->process->getOutput(); 74 | } 75 | 76 | public function getErrorOutput(): string 77 | { 78 | return $this->process->getErrorOutput(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Util/AutoloaderUtil.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Util; 13 | 14 | use Composer\Autoload\ClassLoader; 15 | 16 | /** 17 | * @author Ryan Weaver 18 | * 19 | * @internal 20 | */ 21 | class AutoloaderUtil 22 | { 23 | public function __construct( 24 | private ComposerAutoloaderFinder $autoloaderFinder, 25 | ) { 26 | } 27 | 28 | /** 29 | * Returns the relative path to where a new class should live. 30 | * 31 | * @throws \Exception 32 | */ 33 | public function getPathForFutureClass(string $className): ?string 34 | { 35 | $classLoader = $this->getClassLoader(); 36 | 37 | // lookup is obviously modeled off of Composer's autoload logic 38 | foreach ($classLoader->getPrefixesPsr4() as $prefix => $paths) { 39 | if (str_starts_with($className, $prefix)) { 40 | return $paths[0].'/'.str_replace('\\', '/', substr($className, \strlen($prefix))).'.php'; 41 | } 42 | } 43 | 44 | foreach ($classLoader->getPrefixes() as $prefix => $paths) { 45 | if (str_starts_with($className, $prefix)) { 46 | return $paths[0].'/'.str_replace('\\', '/', $className).'.php'; 47 | } 48 | } 49 | 50 | if ($classLoader->getFallbackDirsPsr4()) { 51 | return $classLoader->getFallbackDirsPsr4()[0].'/'.str_replace('\\', '/', $className).'.php'; 52 | } 53 | 54 | if ($classLoader->getFallbackDirs()) { 55 | return $classLoader->getFallbackDirs()[0].'/'.str_replace('\\', '/', $className).'.php'; 56 | } 57 | 58 | return null; 59 | } 60 | 61 | public function getNamespacePrefixForClass(string $className): string 62 | { 63 | foreach ($this->getClassLoader()->getPrefixesPsr4() as $prefix => $paths) { 64 | if (str_starts_with($className, $prefix)) { 65 | return $prefix; 66 | } 67 | } 68 | 69 | return ''; 70 | } 71 | 72 | /** 73 | * Returns if the namespace is configured by composer autoloader. 74 | */ 75 | public function isNamespaceConfiguredToAutoload(string $namespace): bool 76 | { 77 | $namespace = trim($namespace, '\\').'\\'; 78 | $classLoader = $this->getClassLoader(); 79 | 80 | foreach ($classLoader->getPrefixesPsr4() as $prefix => $paths) { 81 | if (str_starts_with($namespace, $prefix)) { 82 | return true; 83 | } 84 | } 85 | 86 | foreach ($classLoader->getPrefixes() as $prefix => $paths) { 87 | if (str_starts_with($namespace, $prefix)) { 88 | return true; 89 | } 90 | } 91 | 92 | return false; 93 | } 94 | 95 | private function getClassLoader(): ClassLoader 96 | { 97 | return $this->autoloaderFinder->getClassLoader(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Util/ClassDetails.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Util; 13 | 14 | /** 15 | * @internal 16 | */ 17 | final class ClassDetails 18 | { 19 | public function __construct( 20 | private string $fullClassName, 21 | ) { 22 | } 23 | 24 | /** 25 | * Get list of property names except "id" for use in a make:form context. 26 | */ 27 | public function getFormFields(): array 28 | { 29 | $properties = $this->getProperties(); 30 | 31 | $fields = array_diff($properties, ['id']); 32 | 33 | $fieldsWithTypes = []; 34 | foreach ($fields as $field) { 35 | $fieldsWithTypes[$field] = null; 36 | } 37 | 38 | return $fieldsWithTypes; 39 | } 40 | 41 | private function getProperties(): array 42 | { 43 | $reflect = new \ReflectionClass($this->fullClassName); 44 | $props = $reflect->getProperties(); 45 | 46 | $propertiesList = []; 47 | 48 | foreach ($props as $prop) { 49 | $propertiesList[] = $prop->getName(); 50 | } 51 | 52 | return $propertiesList; 53 | } 54 | 55 | public function getPath(): string 56 | { 57 | return (new \ReflectionClass($this->fullClassName))->getFileName(); 58 | } 59 | 60 | public function hasAttribute(string $attributeClassName): bool 61 | { 62 | $reflected = new \ReflectionClass($this->fullClassName); 63 | 64 | foreach ($reflected->getAttributes($attributeClassName) as $reflectedAttribute) { 65 | if ($reflectedAttribute->getName() === $attributeClassName) { 66 | return true; 67 | } 68 | } 69 | 70 | return false; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Util/ClassNameDetails.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Util; 13 | 14 | use Symfony\Bundle\MakerBundle\Str; 15 | 16 | final class ClassNameDetails 17 | { 18 | public function __construct( 19 | private string $fullClassName, 20 | private string $namespacePrefix, 21 | private ?string $suffix = null, 22 | ) { 23 | $this->namespacePrefix = trim($namespacePrefix, '\\'); 24 | } 25 | 26 | public function getFullName(): string 27 | { 28 | return $this->fullClassName; 29 | } 30 | 31 | public function getShortName(): string 32 | { 33 | return Str::getShortClassName($this->fullClassName); 34 | } 35 | 36 | /** 37 | * Returns the original class name the user entered (after 38 | * being cleaned up). 39 | * 40 | * For example, assuming the namespace is App\Entity: 41 | * App\Entity\Admin\User => Admin\User 42 | */ 43 | public function getRelativeName(): string 44 | { 45 | return str_replace($this->namespacePrefix.'\\', '', $this->fullClassName); 46 | } 47 | 48 | public function getRelativeNameWithoutSuffix(): string 49 | { 50 | return Str::removeSuffix($this->getRelativeName(), $this->suffix); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Util/ClassNameValue.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Util; 13 | 14 | use Symfony\Bundle\MakerBundle\Str; 15 | 16 | /** 17 | * @internal 18 | */ 19 | final class ClassNameValue implements \Stringable 20 | { 21 | public function __construct( 22 | private string $typeHint, 23 | private string $fullClassName, 24 | ) { 25 | } 26 | 27 | public function getShortName(): string 28 | { 29 | if ($this->isSelf()) { 30 | return Str::getShortClassName($this->fullClassName); 31 | } 32 | 33 | return $this->typeHint; 34 | } 35 | 36 | public function isSelf(): bool 37 | { 38 | return 'self' === $this->typeHint; 39 | } 40 | 41 | public function __toString(): string 42 | { 43 | return $this->getShortName(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Util/ClassSource/Model/ClassProperty.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Util\ClassSource\Model; 13 | 14 | use Doctrine\ORM\Mapping\FieldMapping; 15 | use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; 16 | 17 | /** 18 | * @author Jesse Rushlow 19 | * 20 | * @internal 21 | */ 22 | final class ClassProperty 23 | { 24 | public function __construct( 25 | public string $propertyName, 26 | public string $type, 27 | public array $comments = [], 28 | public ?int $length = null, 29 | public ?bool $id = null, 30 | public ?bool $nullable = null, 31 | public array $options = [], 32 | public ?int $precision = null, 33 | public ?int $scale = null, 34 | public bool $needsTypeHint = true, 35 | public bool $unique = false, 36 | public ?string $enumType = null, 37 | ) { 38 | } 39 | 40 | public function getAttributes(): array 41 | { 42 | $attributes = []; 43 | 44 | if ($this->needsTypeHint) { 45 | $attributes['type'] = $this->type; 46 | } 47 | 48 | if (!empty($this->options)) { 49 | $attributes['options'] = $this->options; 50 | } 51 | 52 | if ($this->unique) { 53 | $attributes['unique'] = true; 54 | } 55 | 56 | if ($this->enumType) { 57 | $attributes['enumType'] = $this->enumType; 58 | } 59 | 60 | foreach (['length', 'id', 'nullable', 'precision', 'scale'] as $property) { 61 | if (null !== $this->$property) { 62 | $attributes[$property] = $this->$property; 63 | } 64 | } 65 | 66 | return $attributes; 67 | } 68 | 69 | public static function createFromObject(FieldMapping|array $data): self 70 | { 71 | if ($data instanceof FieldMapping) { 72 | return new self( 73 | propertyName: $data->fieldName, 74 | type: $data->type, 75 | length: $data->length, 76 | id: $data->id ?? false, 77 | nullable: $data->nullable ?? false, 78 | options: $data->options ?? [], 79 | precision: $data->precision, 80 | scale: $data->scale, 81 | unique: $data->unique ?? false, 82 | enumType: $data->enumType, 83 | ); 84 | } 85 | 86 | /* @legacy Remove when ORM 2.x is no longer supported. */ 87 | if (empty($data['fieldName']) || empty($data['type'])) { 88 | throw new RuntimeCommandException('Cannot create property model - "fieldName" & "type" are required.'); 89 | } 90 | 91 | return new self( 92 | propertyName: $data['fieldName'], 93 | type: $data['type'], 94 | comments: $data['comments'] ?? [], 95 | length: $data['length'] ?? null, 96 | id: $data['id'] ?? false, 97 | nullable: $data['nullable'] ?? false, 98 | options: $data['options'] ?? [], 99 | precision: $data['precision'] ?? null, 100 | scale: $data['scale'] ?? null, 101 | unique: $data['unique'] ?? false, 102 | enumType: $data['enumType'] ?? null, 103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Util/CliOutputHelper.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Util; 13 | 14 | /** 15 | * Tools used to enhance maker command output. 16 | * 17 | * For additional context with Symfony CLI EnvVars, see 18 | * https://github.com/symfony-cli/symfony-cli/pull/231 19 | * 20 | * @author Jesse Rushlow 21 | * 22 | * @internal 23 | */ 24 | final class CliOutputHelper 25 | { 26 | /** 27 | * EnvVars exposed by Symfony's CLI. 28 | */ 29 | public const ENV_VERSION = 'SYMFONY_CLI_VERSION'; // Current CLI Version 30 | public const ENV_BIN_NAME = 'SYMFONY_CLI_BINARY_NAME'; // Name of the binary e.g. "symfony" 31 | 32 | /** 33 | * Get the correct command prefix based on Symfony CLI usage. 34 | */ 35 | public static function getCommandPrefix(): string 36 | { 37 | $prompt = 'php bin/console'; 38 | 39 | $binaryNameEnvVar = getenv(self::ENV_BIN_NAME); 40 | 41 | if (false !== $binaryNameEnvVar && false !== getenv(self::ENV_VERSION)) { 42 | $prompt = \sprintf('%s console', $binaryNameEnvVar); 43 | } 44 | 45 | return $prompt; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Util/ComposerAutoloaderFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Util; 13 | 14 | use Composer\Autoload\ClassLoader; 15 | use Symfony\Component\Debug\DebugClassLoader; 16 | use Symfony\Component\ErrorHandler\DebugClassLoader as ErrorHandlerDebugClassLoader; 17 | 18 | /** 19 | * @internal 20 | */ 21 | class ComposerAutoloaderFinder 22 | { 23 | private array $rootNamespace; 24 | private ?ClassLoader $classLoader = null; 25 | 26 | public function __construct(string $rootNamespace) 27 | { 28 | $this->rootNamespace = [ 29 | 'psr0' => rtrim($rootNamespace, '\\'), 30 | 'psr4' => rtrim($rootNamespace, '\\').'\\', 31 | ]; 32 | } 33 | 34 | public function getClassLoader(): ClassLoader 35 | { 36 | if (null === $this->classLoader) { 37 | $this->classLoader = $this->findComposerClassLoader(); 38 | } 39 | 40 | if (null === $this->classLoader) { 41 | throw new \Exception("Could not find a Composer autoloader that autoloads from '{$this->rootNamespace['psr4']}'"); 42 | } 43 | 44 | return $this->classLoader; 45 | } 46 | 47 | private function findComposerClassLoader(): ?ClassLoader 48 | { 49 | $autoloadFunctions = spl_autoload_functions(); 50 | 51 | foreach ($autoloadFunctions as $autoloader) { 52 | if (!\is_array($autoloader)) { 53 | continue; 54 | } 55 | 56 | $classLoader = $this->extractComposerClassLoader($autoloader); 57 | if (null === $classLoader) { 58 | continue; 59 | } 60 | 61 | $finalClassLoader = $this->locateMatchingClassLoader($classLoader); 62 | if (null !== $finalClassLoader) { 63 | return $finalClassLoader; 64 | } 65 | } 66 | 67 | return null; 68 | } 69 | 70 | private function extractComposerClassLoader(array $autoloader): ?ClassLoader 71 | { 72 | if (isset($autoloader[0]) && \is_object($autoloader[0])) { 73 | if ($autoloader[0] instanceof ClassLoader) { 74 | return $autoloader[0]; 75 | } 76 | if ( 77 | ($autoloader[0] instanceof DebugClassLoader 78 | || $autoloader[0] instanceof ErrorHandlerDebugClassLoader) 79 | && \is_array($autoloader[0]->getClassLoader()) 80 | && $autoloader[0]->getClassLoader()[0] instanceof ClassLoader) { 81 | return $autoloader[0]->getClassLoader()[0]; 82 | } 83 | } 84 | 85 | return null; 86 | } 87 | 88 | private function locateMatchingClassLoader(ClassLoader $classLoader): ?ClassLoader 89 | { 90 | $makerClassLoader = null; 91 | foreach ($classLoader->getPrefixesPsr4() as $prefix => $paths) { 92 | if ('Symfony\\Bundle\\MakerBundle\\' === $prefix) { 93 | $makerClassLoader = $classLoader; 94 | } 95 | if (str_starts_with($this->rootNamespace['psr4'], $prefix)) { 96 | return $classLoader; 97 | } 98 | } 99 | 100 | foreach ($classLoader->getPrefixes() as $prefix => $paths) { 101 | if (str_starts_with($this->rootNamespace['psr0'], $prefix)) { 102 | return $classLoader; 103 | } 104 | } 105 | 106 | // Nothing found? Try the class loader where we found MakerBundle 107 | return $makerClassLoader; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Util/MakerFileLinkFormatter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Util; 13 | 14 | use Symfony\Component\Console\Formatter\OutputFormatterStyle; 15 | use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; 16 | use Symfony\Component\HttpKernel\Debug\FileLinkFormatter as LegacyFileLinkFormatter; 17 | 18 | /** 19 | * @internal 20 | */ 21 | final class MakerFileLinkFormatter 22 | { 23 | public function __construct( 24 | private FileLinkFormatter|LegacyFileLinkFormatter|null $fileLinkFormatter = null, 25 | ) { 26 | } 27 | 28 | public function makeLinkedPath(string $absolutePath, string $relativePath): string 29 | { 30 | if (!$this->fileLinkFormatter) { 31 | return $relativePath; 32 | } 33 | 34 | if (!$formatted = $this->fileLinkFormatter->format($absolutePath, 1)) { 35 | return $relativePath; 36 | } 37 | 38 | // workaround for difficulties parsing linked file paths in appveyor 39 | if (getenv('MAKER_DISABLE_FILE_LINKS')) { 40 | return $relativePath; 41 | } 42 | 43 | $outputFormatterStyle = new OutputFormatterStyle(); 44 | 45 | if (method_exists(OutputFormatterStyle::class, 'setHref')) { 46 | $outputFormatterStyle->setHref($formatted); 47 | } 48 | 49 | return $outputFormatterStyle->apply($relativePath); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Util/PhpCompatUtil.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Util; 13 | 14 | use Symfony\Bundle\MakerBundle\FileManager; 15 | 16 | /** 17 | * @author Jesse Rushlow 18 | * 19 | * @internal 20 | */ 21 | class PhpCompatUtil 22 | { 23 | public function __construct(private FileManager $fileManager) 24 | { 25 | } 26 | 27 | protected function getPhpVersion(): string 28 | { 29 | $rootDirectory = $this->fileManager->getRootDirectory(); 30 | 31 | $composerLockPath = \sprintf('%s/composer.lock', $rootDirectory); 32 | 33 | if (!$this->fileManager->fileExists($composerLockPath)) { 34 | return \PHP_VERSION; 35 | } 36 | 37 | $lockFileContents = json_decode($this->fileManager->getFileContents($composerLockPath), true); 38 | 39 | if (empty($lockFileContents['platform-overrides']) || empty($lockFileContents['platform-overrides']['php'])) { 40 | return \PHP_VERSION; 41 | } 42 | 43 | return $lockFileContents['platform-overrides']['php']; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Util/PrettyPrinter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Util; 13 | 14 | use PhpParser\Node\Stmt; 15 | use PhpParser\PrettyPrinter\Standard; 16 | 17 | /** 18 | * @internal 19 | */ 20 | final class PrettyPrinter extends Standard 21 | { 22 | /** 23 | * Overridden to fix indentation problem with tabs. 24 | * 25 | * If the original source code uses tabs, then the tokenizer 26 | * will see this as "1" indent level, and will indent new lines 27 | * with just 1 space. By changing 1 indent to 4, we effectively 28 | * "correct" this problem when printing. 29 | * 30 | * For code that is even further indented (e.g. 8 spaces), 31 | * the printer uses the first indentation (here corrected 32 | * from 1 space to 4) and already (without needing any other 33 | * changes) adds 4 spaces onto that. This is why we don't 34 | * also need to handle indent levels of 5, 9, etc: these 35 | * do not occur (at least in the code we generate); 36 | */ 37 | protected function setIndentLevel(int $level): void 38 | { 39 | if (1 === $level) { 40 | $level = 4; 41 | } 42 | 43 | parent::setIndentLevel($level); 44 | } 45 | 46 | /** 47 | * Overridden to change coding standards. 48 | * 49 | * Before: 50 | * public function getFoo() : string 51 | * 52 | * After 53 | * public function getFoo(): string 54 | */ 55 | protected function pStmt_ClassMethod(Stmt\ClassMethod $node): string 56 | { 57 | $classMethod = parent::pStmt_ClassMethod($node); 58 | 59 | if ($node->returnType) { 60 | $classMethod = str_replace(') :', '):', $classMethod); 61 | } 62 | $classMethod = str_replace('\x00', '\0', $classMethod); 63 | 64 | return $classMethod; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Util/TemplateComponentGenerator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Util; 13 | 14 | use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData; 15 | 16 | /** 17 | * @author Jesse Rushlow 18 | * 19 | * @internal 20 | */ 21 | final class TemplateComponentGenerator 22 | { 23 | public function __construct( 24 | private bool $generateFinalClasses, 25 | private bool $generateFinalEntities, 26 | private string $rootNamespace, 27 | ) { 28 | } 29 | 30 | /** 31 | * @param string|null $routePath passing an empty string/null will create a route attribute without the "path" argument 32 | */ 33 | public function generateRouteForControllerMethod(?string $routePath, string $routeName, array $methods = [], bool $indent = true, bool $trailingNewLine = true): string 34 | { 35 | if (!empty($routePath)) { 36 | $path = \sprintf('\'%s\', ', $routePath); 37 | } 38 | 39 | $attribute = \sprintf('%s#[Route(%sname: \'%s\'', $indent ? ' ' : null, $path ?? null, $routeName); 40 | 41 | if (!empty($methods)) { 42 | $attribute .= ', methods: ['; 43 | 44 | foreach ($methods as $method) { 45 | $attribute .= \sprintf('\'%s\', ', $method); 46 | } 47 | 48 | $attribute = rtrim($attribute, ', '); 49 | 50 | $attribute .= ']'; 51 | } 52 | 53 | $attribute .= \sprintf(')]%s', $trailingNewLine ? "\n" : null); 54 | 55 | return $attribute; 56 | } 57 | 58 | public function getPropertyType(ClassNameDetails $classNameDetails): ?string 59 | { 60 | return \sprintf('%s ', $classNameDetails->getShortName()); 61 | } 62 | 63 | public function configureClass(ClassData $classMetadata): ClassData 64 | { 65 | $classMetadata->setRootNamespace($this->rootNamespace); 66 | 67 | if ($classMetadata->isEntity) { 68 | return $classMetadata->setIsFinal($this->generateFinalEntities); 69 | } 70 | 71 | return $classMetadata->setIsFinal($this->generateFinalClasses); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Util/UseStatementGenerator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Util; 13 | 14 | /** 15 | * Converts fully qualified class names into sorted use statements for templates. 16 | * 17 | * @author Jesse Rushlow 18 | * 19 | * @internal 20 | */ 21 | final class UseStatementGenerator implements \Stringable 22 | { 23 | /** 24 | * For use statements that contain aliases, the $classesToBeImported array 25 | * may contain an array(s) like [\Some\Class::class => 'ZYX']. The generated 26 | * use statement would appear as "use Some\Class::class as 'ZXY'". It is ok 27 | * to mix non-aliases classes with aliases. 28 | * 29 | * @param string[]|array $classesToBeImported 30 | */ 31 | public function __construct( 32 | private array $classesToBeImported, 33 | ) { 34 | } 35 | 36 | public function __toString(): string 37 | { 38 | $transformed = []; 39 | $aliases = []; 40 | 41 | foreach ($this->classesToBeImported as $key => $class) { 42 | if (\is_array($class)) { 43 | $aliasClass = key($class); 44 | $aliases[$aliasClass] = $class[$aliasClass]; 45 | $class = $aliasClass; 46 | } 47 | 48 | $transformedClass = str_replace('\\', ' ', $class); 49 | // Let's not add the class again if it already exists. 50 | if (!\in_array($transformedClass, $transformed, true)) { 51 | $transformed[$key] = $transformedClass; 52 | } 53 | } 54 | 55 | asort($transformed); 56 | 57 | $statements = ''; 58 | 59 | foreach ($transformed as $key => $class) { 60 | $importedClass = $this->classesToBeImported[$key]; 61 | 62 | if (!\is_array($importedClass)) { 63 | $statements .= \sprintf("use %s;\n", $importedClass); 64 | continue; 65 | } 66 | 67 | $aliasClass = key($importedClass); 68 | $statements .= \sprintf("use %s as %s;\n", $aliasClass, $aliases[$aliasClass]); 69 | } 70 | 71 | return $statements; 72 | } 73 | 74 | /** 75 | * @param string|string[]|array $className 76 | */ 77 | public function addUseStatement(array|string $className): void 78 | { 79 | if (\is_array($className)) { 80 | $this->classesToBeImported = array_merge($this->classesToBeImported, $className); 81 | 82 | return; 83 | } 84 | 85 | // Let's not add the class again if it already exists. 86 | if (\in_array($className, $this->classesToBeImported, true)) { 87 | return; 88 | } 89 | 90 | $this->classesToBeImported[] = $className; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Util/YamlManipulationFailedException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Bundle\MakerBundle\Util; 13 | 14 | /** 15 | * Thrown whenever YamlSourceManipulator cannot change contents successfully. 16 | */ 17 | class YamlManipulationFailedException extends \RuntimeException 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /templates/Class.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | class 6 | { 7 | } 8 | -------------------------------------------------------------------------------- /templates/authenticator/EmptyAuthenticator.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | class extends AbstractAuthenticator 8 | { 9 | public function supports(Request $request): ?bool 10 | { 11 | // TODO: Implement supports() method. 12 | } 13 | 14 | public function authenticate(Request $request): Passport 15 | { 16 | // TODO: Implement authenticate() method. 17 | } 18 | 19 | public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response 20 | { 21 | // TODO: Implement onAuthenticationSuccess() method. 22 | } 23 | 24 | public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response 25 | { 26 | // TODO: Implement onAuthenticationFailure() method. 27 | } 28 | 29 | // public function start(Request $request, ?AuthenticationException $authException = null): Response 30 | // { 31 | // /* 32 | // * If you would like this class to control what happens when an anonymous user accesses a 33 | // * protected page (e.g. redirect to /login), uncomment this method and make this class 34 | // * implement Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface. 35 | // * 36 | // * For more details, see https://symfony.com/doc/current/security/experimental_authenticators.html#configuring-the-authentication-entry-point 37 | // */ 38 | // } 39 | } 40 | -------------------------------------------------------------------------------- /templates/authenticator/EmptySecurityController.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | class extends AbstractController 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /templates/authenticator/LoginFormAuthenticator.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | class extends AbstractLoginFormAuthenticator 8 | { 9 | use TargetPathTrait; 10 | 11 | public const LOGIN_ROUTE = 'app_login'; 12 | 13 | public function __construct(private UrlGeneratorInterface $urlGenerator) 14 | { 15 | } 16 | 17 | public function authenticate(Request $request): Passport 18 | { 19 | $ = $request->getPayload()->getString(''); 20 | 21 | $request->getSession()->set(SecurityRequestAttributes::LAST_USERNAME, $); 22 | 23 | return new Passport( 24 | new UserBadge($), 25 | new PasswordCredentials($request->getPayload()->getString('password')), 26 | [ 27 | new CsrfTokenBadge('authenticate', $request->getPayload()->getString('_csrf_token')), 29 | ] 30 | ); 31 | } 32 | 33 | public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response 34 | { 35 | if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) { 36 | return new RedirectResponse($targetPath); 37 | } 38 | 39 | // For example: 40 | // return new RedirectResponse($this->urlGenerator->generate('some_route')); 41 | throw new \Exception('TODO: provide a valid redirect inside '.__FILE__); 42 | } 43 | 44 | protected function getLoginUrl(Request $request): string 45 | { 46 | return $this->urlGenerator->generate(self::LOGIN_ROUTE); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /templates/authenticator/login_form.tpl.php: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block title %}Log in!{% endblock %} 4 | 5 | {% block body %} 6 |
7 | {% if error %} 8 |
{{ error.messageKey|trans(error.messageData, 'security') }}
9 | {% endif %} 10 | 11 | 12 | {% if app.user %} 13 |
14 | You are logged in as {{ app.user.userIdentifier }}, Logout 15 |
16 | {% endif %} 17 | 18 | 19 |

Please sign in

20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 31 |
32 | 33 | 34 | 37 |
38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /templates/command/Command.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | #[AsCommand( 8 | name: '', 9 | description: 'Add a short description for your command', 10 | )] 11 | class extends Command 12 | { 13 | public function __construct() 14 | { 15 | parent::__construct(); 16 | } 17 | 18 | protected function configure(): void 19 | { 20 | $this 21 | setDescription(self::\$defaultDescription)\n" : '' ?> 22 | ->addArgument('arg1', InputArgument::OPTIONAL, 'Argument description') 23 | ->addOption('option1', null, InputOption::VALUE_NONE, 'Option description') 24 | ; 25 | } 26 | 27 | protected function execute(InputInterface $input, OutputInterface $output): int 28 | { 29 | $io = new SymfonyStyle($input, $output); 30 | $arg1 = $input->getArgument('arg1'); 31 | 32 | if ($arg1) { 33 | $io->note(sprintf('You passed an argument: %s', $arg1)); 34 | } 35 | 36 | if ($input->getOption('option1')) { 37 | // ... 38 | } 39 | 40 | $io->success('You have a new command! Now make it your own! Pass --help to see your options.'); 41 | 42 | return Command::SUCCESS; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /templates/controller/Controller.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace getNamespace(); ?>; 4 | 5 | getUseStatements(); ?> 6 | 7 | getClassDeclaration(); ?> 8 | 9 | { 10 | generateRouteForControllerMethod($route_path, $route_name); ?> 11 | public function (): ResponseJsonResponse 12 | 13 | { 14 | 15 | return $this->render('', [ 16 | 'controller_name' => 'getClassName() ?>', 17 | ]); 18 | 19 | return $this->json([ 20 | 'message' => 'Welcome to your new controller!', 21 | 'path' => '', 22 | ]); 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /templates/controller/test/Test.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace getNamespace(); ?>; 4 | 5 | getUseStatements(); ?> 6 | 7 | getClassDeclaration(); ?> 8 | 9 | { 10 | public function testIndex(): void 11 | { 12 | $client = static::createClient(); 13 | $client->request('GET', ''); 14 | 15 | self::assertResponseIsSuccessful(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /templates/controller/twig_template.tpl.php: -------------------------------------------------------------------------------- 1 | getHeadPrintCode("Hello $class_name!"); ?> 2 | 3 | {% block body %} 4 | 8 | 9 |
10 |

Hello {{ controller_name }}! ✅

11 | 12 | This friendly message is coming from: 13 |
    14 |
  • Your controller at /
  • 15 |
  • Your template at /
  • 16 |
17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /templates/crud/templates/_delete_form.tpl.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /templates/crud/templates/_form.tpl.php: -------------------------------------------------------------------------------- 1 | {{ form_start(form) }} 2 | {{ form_widget(form) }} 3 | 4 | {{ form_end(form) }} 5 | -------------------------------------------------------------------------------- /templates/crud/templates/edit.tpl.php: -------------------------------------------------------------------------------- 1 | getHeadPrintCode('Edit '.$entity_class_name) ?> 2 | 3 | {% block body %} 4 |

Edit

5 | 6 | {{ include('/_form.html.twig', {'button_label': 'Update'}) }} 7 | 8 | back to list 9 | 10 | {{ include('/_delete_form.html.twig') }} 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /templates/crud/templates/index.tpl.php: -------------------------------------------------------------------------------- 1 | getHeadPrintCode($entity_class_name.' index'); ?> 2 | 3 | {% block body %} 4 |

index

5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% for in %} 17 | 18 | 19 | 20 | 21 | 25 | 26 | {% else %} 27 | 28 | 29 | 30 | {% endfor %} 31 | 32 |
actions
{{ getEntityFieldPrintCode($entity_twig_var_singular, $field) ?> }} 22 | show 23 | edit 24 |
no records found
33 | 34 | Create new 35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /templates/crud/templates/new.tpl.php: -------------------------------------------------------------------------------- 1 | getHeadPrintCode('New '.$entity_class_name) ?> 2 | 3 | {% block body %} 4 |

Create new

5 | 6 | {{ include('/_form.html.twig') }} 7 | 8 | back to list 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /templates/crud/templates/show.tpl.php: -------------------------------------------------------------------------------- 1 | getHeadPrintCode($entity_class_name) ?> 2 | 3 | {% block body %} 4 |

5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
{{ getEntityFieldPrintCode($entity_twig_var_singular, $field) ?> }}
16 | 17 | back to list 18 | 19 | edit 20 | 21 | {{ include('/_delete_form.html.twig') }} 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /templates/doctrine/Entity.tpl.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | namespace ; 9 | 10 | 11 | 12 | #[ORM\Entity(repositoryClass: ::class)] 13 | #[ORM\Table(name: '``')] 14 | 15 | 16 | #[ApiResource] 17 | 18 | 19 | #[Broadcast] 20 | 21 | class 22 | { 23 | 24 | #[ORM\Id] 25 | #[ORM\Column(type: UuidType::NAME, unique: true)] 26 | #[ORM\GeneratedValue(strategy: 'CUSTOM')] 27 | #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')] 28 | private ?Uuid $id = null; 29 | 30 | public function getId(): ?Uuid 31 | { 32 | return $this->id; 33 | } 34 | 35 | #[ORM\Id] 36 | #[ORM\Column(type: UlidType::NAME, unique: true)] 37 | #[ORM\GeneratedValue(strategy: 'CUSTOM')] 38 | #[ORM\CustomIdGenerator(class: 'doctrine.ulid_generator')] 39 | private ?Ulid $id = null; 40 | 41 | public function getId(): ?Ulid 42 | { 43 | return $this->id; 44 | } 45 | 46 | #[ORM\Id] 47 | #[ORM\GeneratedValue] 48 | #[ORM\Column] 49 | private ?int $id = null; 50 | 51 | public function getId(): ?int 52 | { 53 | return $this->id; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /templates/doctrine/Fixtures.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | class extends Fixture 8 | { 9 | public function load(ObjectManager $manager): void 10 | { 11 | // $product = new Product(); 12 | // $manager->persist($product); 13 | 14 | $manager->flush(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /templates/doctrine/Repository.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | /** 8 | * @extends ServiceEntityRepository<> 9 | */ 10 | class extends ServiceEntityRepository 11 | { 12 | public function __construct(ManagerRegistry $registry) 13 | { 14 | parent::__construct($registry, ::class); 15 | } 16 | 17 | 18 | 19 | 20 | /** 21 | * Used to upgrade (rehash) the user's password automatically over time. 22 | */ 23 | public function upgradePassword(getShortName()); ?>$user, string $newHashedPassword): void 24 | { 25 | if (!$user instanceof ) { 26 | throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class)); 27 | } 28 | 29 | $user->setPassword($newHashedPassword); 30 | $this->getEntityManager()->persist($user); 31 | $this->getEntityManager()->flush(); 32 | } 33 | 34 | 35 | 36 | // /** 37 | // * @return [] Returns an array of objects 38 | // */ 39 | // public function findByExampleField($value): array 40 | // { 41 | // return $this->createQueryBuilder('') 42 | // ->andWhere('.exampleField = :val') 43 | // ->setParameter('val', $value) 44 | // ->orderBy('.id', 'ASC') 45 | // ->setMaxResults(10) 46 | // ->getQuery() 47 | // ->getResult() 48 | // ; 49 | // } 50 | 51 | // public function findOneBySomeField($value): ? 52 | // { 53 | // return $this->createQueryBuilder('') 54 | // ->andWhere('.exampleField = :val') 55 | // ->setParameter('val', $value) 56 | // ->getQuery() 57 | // ->getOneOrNullResult() 58 | // ; 59 | // } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /templates/doctrine/broadcast_twig_template.tpl.php: -------------------------------------------------------------------------------- 1 | {# Learn how to use Turbo Streams: https://github.com/symfony/ux-turbo#broadcast-doctrine-entities-update #} 2 | {% block create %} 3 | 4 | 9 | 10 | {% endblock %} 11 | 12 | {% block update %} 13 | 14 | 17 | 18 | {% endblock %} 19 | 20 | {% block remove %} 21 | 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /templates/event/Listener.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | final class 8 | { 9 | #[AsEventListener(event: )] 10 | public function (): void 11 | { 12 | // ... 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /templates/event/Subscriber.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | class implements EventSubscriberInterface 8 | { 9 | public function (): void 10 | { 11 | // ... 12 | } 13 | 14 | public static function getSubscribedEvents(): array 15 | { 16 | return [ 17 | => '', 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /templates/form/Form.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | class extends AbstractType 8 | { 9 | public function buildForm(FormBuilderInterface $builder, array $options): void 10 | { 11 | $builder 12 | $typeOptions): ?> 13 | 14 | ->add('') 15 | 16 | ->add('', ::class) 17 | 18 | ->add('', , [ 19 | 20 | ]) 21 | 22 | 23 | ; 24 | } 25 | 26 | public function configureOptions(OptionsResolver $resolver): void 27 | { 28 | $resolver->setDefaults([ 29 | 30 | 'data_class' => ::class, 31 | 32 | // Configure your form options here 33 | 34 | ]); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /templates/message/Message.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | #[AsMessage('')] 8 | final class 9 | { 10 | /* 11 | * Add whatever properties and methods you need 12 | * to hold the data for this message class. 13 | */ 14 | 15 | // public function __construct( 16 | // public readonly string $name, 17 | // ) { 18 | // } 19 | } 20 | -------------------------------------------------------------------------------- /templates/message/MessageHandler.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | #[AsMessageHandler] 8 | final class 9 | { 10 | public function __invoke( $message): void 11 | { 12 | // do something with your message 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /templates/middleware/Middleware.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | final class implements MiddlewareInterface 8 | { 9 | public function handle(Envelope $envelope, StackInterface $stack): Envelope 10 | { 11 | // ... 12 | return $stack->next()->handle($envelope, $stack); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /templates/registration/Test.WithVerify.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | namespace App\Tests; 3 | 4 | 5 | 6 | class RegistrationControllerTest extends WebTestCase 7 | { 8 | private KernelBrowser $client; 9 | private $userRepository; 10 | 11 | protected function setUp(): void 12 | { 13 | $this->client = static::createClient(); 14 | 15 | // Ensure we have a clean database 16 | $container = static::getContainer(); 17 | 18 | /** @var EntityManager $em */ 19 | $em = $container->get('doctrine')->getManager(); 20 | $this->userRepository = $container->get(::class); 21 | 22 | foreach ($this->userRepository->findAll() as $user) { 23 | $em->remove($user); 24 | } 25 | 26 | $em->flush(); 27 | } 28 | 29 | public function testRegister(): void 30 | { 31 | // Register a new user 32 | $this->client->request('GET', '/register'); 33 | self::assertResponseIsSuccessful(); 34 | self::assertPageTitleContains('Register'); 35 | 36 | $this->client->submitForm('Register', [ 37 | 'registration_form[email]' => 'me@example.com', 38 | 'registration_form[plainPassword]' => 'password', 39 | 'registration_form[agreeTerms]' => true, 40 | ]); 41 | 42 | // Ensure the response redirects after submitting the form, the user exists, and is not verified 43 | // self::assertResponseRedirects('/'); @TODO: set the appropriate path that the user is redirected to. 44 | self::assertCount(1, $this->userRepository->findAll()); 45 | self::assertFalse(($user = $this->userRepository->findAll()[0])->isVerified()); 46 | 47 | // Ensure the verification email was sent 48 | // Use either assertQueuedEmailCount() || assertEmailCount() depending on your mailer setup 49 | // self::assertQueuedEmailCount(1); 50 | self::assertEmailCount(1); 51 | 52 | self::assertCount(1, $messages = $this->getMailerMessages()); 53 | self::assertEmailAddressContains($messages[0], 'from', ''); 54 | self::assertEmailAddressContains($messages[0], 'to', 'me@example.com'); 55 | self::assertEmailTextBodyContains($messages[0], 'This link will expire in 1 hour.'); 56 | 57 | // Login the new user 58 | $this->client->followRedirect(); 59 | $this->client->loginUser($user); 60 | 61 | // Get the verification link from the email 62 | /** @var TemplatedEmail $templatedEmail */ 63 | $templatedEmail = $messages[0]; 64 | $messageBody = $templatedEmail->getHtmlBody(); 65 | self::assertIsString($messageBody); 66 | 67 | preg_match('#(http://localhost/verify/email.+)">#', $messageBody, $resetLink); 68 | 69 | // "Click" the link and see if the user is verified 70 | $this->client->request('GET', $resetLink[1]); 71 | $this->client->followRedirect(); 72 | 73 | self::assertTrue(static::getContainer()->get(::class)->findAll()[0]->isVerified()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /templates/registration/Test.WithoutVerify.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | namespace App\Tests; 3 | 4 | 5 | 6 | class RegistrationControllerTest extends WebTestCase 7 | { 8 | private KernelBrowser $client; 9 | private $userRepository; 10 | 11 | protected function setUp(): void 12 | { 13 | $this->client = static::createClient(); 14 | 15 | // Ensure we have a clean database 16 | $container = static::getContainer(); 17 | 18 | /** @var EntityManager $em */ 19 | $em = $container->get('doctrine')->getManager(); 20 | $this->userRepository = $container->get(::class); 21 | 22 | foreach ($this->userRepository->findAll() as $user) { 23 | $em->remove($user); 24 | } 25 | 26 | $em->flush(); 27 | } 28 | 29 | public function testRegister(): void 30 | { 31 | // Register a new user 32 | $this->client->request('GET', '/register'); 33 | self::assertResponseIsSuccessful(); 34 | self::assertPageTitleContains('Register'); 35 | 36 | $this->client->submitForm('Register', [ 37 | 'registration_form[email]' => 'me@example.com', 38 | 'registration_form[plainPassword]' => 'password', 39 | 'registration_form[agreeTerms]' => true, 40 | ]); 41 | 42 | // Ensure the response redirects after submitting the form, the user exists, and is not verified 43 | // self::assertResponseRedirects('/'); @TODO: set the appropriate path that the user is redirected to. 44 | self::assertCount(1, $this->userRepository->findAll()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /templates/registration/twig_email.tpl.php: -------------------------------------------------------------------------------- 1 |

Hi! Please confirm your email!

2 | 3 |

4 | Please confirm your email address by clicking the following link:

5 | Confirm my Email. 6 | This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}. 7 |

8 | 9 |

10 | Cheers! 11 |

12 | -------------------------------------------------------------------------------- /templates/registration/twig_template.tpl.php: -------------------------------------------------------------------------------- 1 | getHeadPrintCode('Register'); ?> 2 | 3 | {% block body %} 4 | 5 | {% for flash_error in app.flashes('verify_email_error') %} 6 | 7 | {% endfor %} 8 | 9 | 10 |

Register

11 | 12 | {{ form_errors(registrationForm) }} 13 | 14 | {{ form_start(registrationForm) }} 15 | {{ form_row(registrationForm.) }} 16 | {{ form_row(registrationForm.plainPassword, { 17 | label: 'Password' 18 | }) }} 19 | {{ form_row(registrationForm.agreeTerms) }} 20 | 21 | 22 | {{ form_end(registrationForm) }} 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /templates/resetPassword/ChangePasswordForm.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | class extends AbstractType 8 | { 9 | public function buildForm(FormBuilderInterface $builder, array $options): void 10 | { 11 | $builder 12 | ->add('plainPassword', RepeatedType::class, [ 13 | 'type' => PasswordType::class, 14 | 'options' => [ 15 | 'attr' => [ 16 | 'autocomplete' => 'new-password', 17 | ], 18 | ], 19 | 'first_options' => [ 20 | 'constraints' => [ 21 | new NotBlank([ 22 | 'message' => 'Please enter a password', 23 | ]), 24 | new Length([ 25 | 'min' => 12, 26 | 'minMessage' => 'Your password should be at least {{ limit }} characters', 27 | // max length allowed by Symfony for security reasons 28 | 'max' => 4096, 29 | ]), 30 | new PasswordStrength(), 31 | new NotCompromisedPassword(), 32 | ], 33 | 'label' => 'New password', 34 | ], 35 | 'second_options' => [ 36 | 'label' => 'Repeat Password', 37 | ], 38 | 'invalid_message' => 'The password fields must match.', 39 | // Instead of being set onto the object directly, 40 | // this is read and encoded in the controller 41 | 'mapped' => false, 42 | ]) 43 | ; 44 | } 45 | 46 | public function configureOptions(OptionsResolver $resolver): void 47 | { 48 | $resolver->setDefaults([]); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /templates/resetPassword/ResetPasswordRequestForm.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | class extends AbstractType 8 | { 9 | public function buildForm(FormBuilderInterface $builder, array $options): void 10 | { 11 | $builder 12 | ->add('', EmailType::class, [ 13 | 'attr' => ['autocomplete' => 'email'], 14 | 'constraints' => [ 15 | new NotBlank([ 16 | 'message' => 'Please enter your email', 17 | ]), 18 | ], 19 | ]) 20 | ; 21 | } 22 | 23 | public function configureOptions(OptionsResolver $resolver): void 24 | { 25 | $resolver->setDefaults([]); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /templates/resetPassword/Test.ResetPasswordController.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | namespace App\Tests; 3 | 4 | 5 | 6 | class ResetPasswordControllerTest extends WebTestCase 7 | { 8 | private KernelBrowser $client; 9 | private EntityManagerInterface $em; 10 | private $userRepository; 11 | 12 | protected function setUp(): void 13 | { 14 | $this->client = static::createClient(); 15 | 16 | // Ensure we have a clean database 17 | $container = static::getContainer(); 18 | 19 | /** @var EntityManagerInterface $em */ 20 | $em = $container->get('doctrine')->getManager(); 21 | $this->em = $em; 22 | 23 | $this->userRepository = $container->get(::class); 24 | 25 | foreach ($this->userRepository->findAll() as $user) { 26 | $this->em->remove($user); 27 | } 28 | 29 | $this->em->flush(); 30 | } 31 | 32 | public function testResetPasswordController(): void 33 | { 34 | // Create a test user 35 | $user = (new ()) 36 | ->setEmail('me@example.com') 37 | ->setPassword('a-test-password-that-will-be-changed-later') 38 | ; 39 | $this->em->persist($user); 40 | $this->em->flush(); 41 | 42 | // Test Request reset password page 43 | $this->client->request('GET', '/reset-password'); 44 | 45 | self::assertResponseIsSuccessful(); 46 | self::assertPageTitleContains('Reset your password'); 47 | 48 | // Submit the reset password form and test email message is queued / sent 49 | $this->client->submitForm('Send password reset email', [ 50 | 'reset_password_request_form[email]' => 'me@example.com', 51 | ]); 52 | 53 | // Ensure the reset password email was sent 54 | // Use either assertQueuedEmailCount() || assertEmailCount() depending on your mailer setup 55 | // self::assertQueuedEmailCount(1); 56 | self::assertEmailCount(1); 57 | 58 | self::assertCount(1, $messages = $this->getMailerMessages()); 59 | 60 | self::assertEmailAddressContains($messages[0], 'from', ''); 61 | self::assertEmailAddressContains($messages[0], 'to', 'me@example.com'); 62 | self::assertEmailTextBodyContains($messages[0], 'This link will expire in 1 hour.'); 63 | 64 | self::assertResponseRedirects('/reset-password/check-email'); 65 | 66 | // Test check email landing page shows correct "expires at" time 67 | $crawler = $this->client->followRedirect(); 68 | 69 | self::assertPageTitleContains('Password Reset Email Sent'); 70 | self::assertStringContainsString('This link will expire in 1 hour', $crawler->html()); 71 | 72 | // Test the link sent in the email is valid 73 | $email = $messages[0]->toString(); 74 | preg_match('#(/reset-password/reset/[a-zA-Z0-9]+)#', $email, $resetLink); 75 | 76 | $this->client->request('GET', $resetLink[1]); 77 | 78 | self::assertResponseRedirects('/reset-password/reset'); 79 | 80 | $this->client->followRedirect(); 81 | 82 | // Test we can set a new password 83 | $this->client->submitForm('Reset password', [ 84 | 'change_password_form[plainPassword][first]' => 'newStrongPassword', 85 | 'change_password_form[plainPassword][second]' => 'newStrongPassword', 86 | ]); 87 | 88 | self::assertResponseRedirects(''); 89 | 90 | $user = $this->userRepository->findOneBy(['email' => 'me@example.com']); 91 | 92 | self::assertInstanceOf(::class, $user); 93 | 94 | /** @var UserPasswordHasherInterface $passwordHasher */ 95 | $passwordHasher = static::getContainer()->get(UserPasswordHasherInterface::class); 96 | self::assertTrue($passwordHasher->isPasswordValid($user, 'newStrongPassword')); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /templates/resetPassword/twig_check_email.tpl.php: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block title %}Password Reset Email Sent{% endblock %} 4 | 5 | {% block body %} 6 |

7 | If an account matching your email exists, then an email was just sent that contains a link that you can use to reset your password. 8 | This link will expire in {{ resetToken.expirationMessageKey|trans(resetToken.expirationMessageData, 'ResetPasswordBundle') }}. 9 |

10 |

If you don't receive an email please check your spam folder or try again.

11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /templates/resetPassword/twig_email.tpl.php: -------------------------------------------------------------------------------- 1 |

Hi!

2 | 3 |

To reset your password, please visit the following link

4 | 5 | {{ url('app_reset_password', {token: resetToken.token}) }} 6 | 7 |

This link will expire in {{ resetToken.expirationMessageKey|trans(resetToken.expirationMessageData, 'ResetPasswordBundle') }}.

8 | 9 |

Cheers!

10 | -------------------------------------------------------------------------------- /templates/resetPassword/twig_request.tpl.php: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block title %}Reset your password{% endblock %} 4 | 5 | {% block body %} 6 | {% for flash_error in app.flashes('reset_password_error') %} 7 | 8 | {% endfor %} 9 |

Reset your password

10 | 11 | {{ form_start(requestForm) }} 12 | {{ form_row(requestForm.) }} 13 |
14 | 15 | Enter your email address, and we will send you a 16 | link to reset your password. 17 | 18 |
19 | 20 | 21 | {{ form_end(requestForm) }} 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /templates/resetPassword/twig_reset.tpl.php: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block title %}Reset your password{% endblock %} 4 | 5 | {% block body %} 6 |

Reset your password

7 | 8 | {{ form_start(resetForm) }} 9 | {{ form_row(resetForm.plainPassword) }} 10 | 11 | {{ form_end(resetForm) }} 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /templates/scheduler/Schedule.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | #[AsSchedule('')] 8 | final class implements ScheduleProviderInterface 9 | { 10 | public function __construct( 11 | private CacheInterface $cache, 12 | ) { 13 | } 14 | 15 | public function getSchedule(): Schedule 16 | { 17 | return (new Schedule()) 18 | ->add( 19 | 20 | // @TODO - Modify the frequency to suite your needs 21 | RecurringMessage::every('1 hour', new ()), 22 | 23 | // @TODO - Create a Message to schedule 24 | // RecurringMessage::every('1 hour', new App\Message\Message()), 25 | 26 | ) 27 | ->stateful($this->cache) 28 | ; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /templates/security/UserProvider.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | class implements UserProviderInterface, PasswordUpgraderInterface 8 | { 9 | /** 10 | * Symfony calls this method if you use features like switch_user 11 | * or remember_me. 12 | * 13 | * If you're not using these features, you do not need to implement 14 | * this method. 15 | * 16 | * @throws UserNotFoundException if the user is not found 17 | */ 18 | public function loadUserByIdentifier($identifier): UserInterface 19 | { 20 | // Load a User object from your data source or throw UserNotFoundException. 21 | // The $identifier argument may not actually be a username: 22 | // it is whatever value is being returned by the getUserIdentifier() 23 | // method in your User class. 24 | throw new \Exception('TODO: fill in loadUserByIdentifier() inside '.__FILE__); 25 | } 26 | 27 | /** 28 | * @deprecated since Symfony 5.3, loadUserByIdentifier() is used instead 29 | */ 30 | public function loadUserByUsername($username): UserInterface 31 | { 32 | return $this->loadUserByIdentifier($username); 33 | } 34 | 35 | /** 36 | * Refreshes the user after being reloaded from the session. 37 | * 38 | * When a user is logged in, at the beginning of each request, the 39 | * User object is loaded from the session and then this method is 40 | * called. Your job is to make sure the user's data is still fresh by, 41 | * for example, re-querying for fresh User data. 42 | * 43 | * If your firewall is "stateless: true" (for a pure API), this 44 | * method is not called. 45 | */ 46 | public function refreshUser(UserInterface $user): UserInterface 47 | { 48 | if (!$user instanceof ) { 49 | throw new UnsupportedUserException(sprintf('Invalid user class "%s".', $user::class)); 50 | } 51 | 52 | // Return a User object after making sure its data is "fresh". 53 | // Or throw a UsernameNotFoundException if the user no longer exists. 54 | throw new \Exception('TODO: fill in refreshUser() inside '.__FILE__); 55 | } 56 | 57 | /** 58 | * Tells Symfony to use this provider for this User class. 59 | */ 60 | public function supportsClass(string $class): bool 61 | { 62 | return ::class === $class || is_subclass_of($class, ::class); 63 | } 64 | 65 | /** 66 | * Upgrades the hashed password of a user, typically for using a better hash algorithm. 67 | */ 68 | public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void 69 | { 70 | // TODO: when hashed passwords are in use, this method should: 71 | // 1. persist the new password in the user storage 72 | // 2. update the $user object with $user->setPassword($newHashedPassword); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /templates/security/Voter.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace getNamespace(); ?>; 4 | 5 | getUseStatements(); ?> 6 | 7 | getClassDeclaration() ?> 8 | 9 | { 10 | public const EDIT = 'POST_EDIT'; 11 | public const VIEW = 'POST_VIEW'; 12 | 13 | protected function supports(string $attribute, mixed $subject): bool 14 | { 15 | // replace with your own logic 16 | // https://symfony.com/doc/current/security/voters.html 17 | return in_array($attribute, [self::EDIT, self::VIEW]) 18 | && $subject instanceof \App\Entity\getClassName()) ?>; 19 | } 20 | 21 | protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool 22 | { 23 | $user = $token->getUser(); 24 | // if the user is anonymous, do not grant access 25 | if (!$user instanceof UserInterface) { 26 | return false; 27 | } 28 | 29 | // ... (check conditions and return true to grant permission) ... 30 | switch ($attribute) { 31 | case self::EDIT: 32 | // logic to determine if the user can EDIT 33 | // return true or false 34 | break; 35 | case self::VIEW: 36 | // logic to determine if the user can VIEW 37 | // return true or false 38 | break; 39 | } 40 | 41 | return false; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /templates/security/custom/Authenticator.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | /** 8 | * @see https://symfony.com/doc/current/security/custom_authenticator.html 9 | */ 10 | class extends AbstractAuthenticator 11 | { 12 | /** 13 | * Called on every request to decide if this authenticator should be 14 | * used for the request. Returning `false` will cause this authenticator 15 | * to be skipped. 16 | */ 17 | public function supports(Request $request): ?bool 18 | { 19 | // return $request->headers->has('X-AUTH-TOKEN'); 20 | } 21 | 22 | public function authenticate(Request $request): Passport 23 | { 24 | // $apiToken = $request->headers->get('X-AUTH-TOKEN'); 25 | // if (null === $apiToken) { 26 | // The token header was empty, authentication fails with HTTP Status 27 | // Code 401 "Unauthorized" 28 | // throw new CustomUserMessageAuthenticationException('No API token provided'); 29 | // } 30 | 31 | // implement your own logic to get the user identifier from `$apiToken` 32 | // e.g. by looking up a user in the database using its API key 33 | // $userIdentifier = /** ... */; 34 | 35 | // return new SelfValidatingPassport(new UserBadge($userIdentifier)); 36 | } 37 | 38 | public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response 39 | { 40 | // on success, let the request continue 41 | return null; 42 | } 43 | 44 | public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response 45 | { 46 | $data = [ 47 | // you may want to customize or obfuscate the message first 48 | 'message' => strtr($exception->getMessageKey(), $exception->getMessageData()) 49 | 50 | // or to translate this message 51 | // $this->translator->trans($exception->getMessageKey(), $exception->getMessageData()) 52 | ]; 53 | 54 | return new JsonResponse($data, Response::HTTP_UNAUTHORIZED); 55 | } 56 | 57 | // public function start(Request $request, ?AuthenticationException $authException = null): Response 58 | // { 59 | // /* 60 | // * If you would like this class to control what happens when an anonymous user accesses a 61 | // * protected page (e.g. redirect to /login), uncomment this method and make this class 62 | // * implement Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface. 63 | // * 64 | // * For more details, see https://symfony.com/doc/current/security/experimental_authenticators.html#configuring-the-authentication-entry-point 65 | // */ 66 | // } 67 | } 68 | -------------------------------------------------------------------------------- /templates/security/formLogin/LoginController.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | class extends AbstractController 8 | { 9 | #[Route(path: '/login', name: 'app_login')] 10 | public function login(AuthenticationUtils $authenticationUtils): Response 11 | { 12 | // get the login error if there is one 13 | $error = $authenticationUtils->getLastAuthenticationError(); 14 | 15 | // last username entered by the user 16 | $lastUsername = $authenticationUtils->getLastUsername(); 17 | 18 | return $this->render('/login.html.twig', [ 19 | 'last_username' => $lastUsername, 20 | 'error' => $error, 21 | ]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /templates/security/formLogin/Test.LoginController.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | namespace App\Tests; 3 | 4 | 5 | 6 | class LoginControllerTest extends WebTestCase 7 | { 8 | private KernelBrowser $client; 9 | 10 | protected function setUp(): void 11 | { 12 | $this->client = static::createClient(); 13 | $container = static::getContainer(); 14 | $em = $container->get('doctrine.orm.entity_manager'); 15 | $userRepository = $em->getRepository(::class); 16 | 17 | // Remove any existing users from the test database 18 | foreach ($userRepository->findAll() as $user) { 19 | $em->remove($user); 20 | } 21 | 22 | $em->flush(); 23 | 24 | // Create a fixture 25 | /** @var UserPasswordHasherInterface $passwordHasher */ 26 | $passwordHasher = $container->get('security.user_password_hasher'); 27 | 28 | $user = (new ())->setEmail('email@example.com'); 29 | $user->setPassword($passwordHasher->hashPassword($user, 'password')); 30 | 31 | $em->persist($user); 32 | $em->flush(); 33 | } 34 | 35 | public function testLogin(): void 36 | { 37 | // Denied - Can't login with invalid email address. 38 | $this->client->request('GET', '/login'); 39 | self::assertResponseIsSuccessful(); 40 | 41 | $this->client->submitForm('Sign in', [ 42 | '_username' => 'doesNotExist@example.com', 43 | '_password' => 'password', 44 | ]); 45 | 46 | self::assertResponseRedirects('/login'); 47 | $this->client->followRedirect(); 48 | 49 | // Ensure we do not reveal if the user exists or not. 50 | self::assertSelectorTextContains('.alert-danger', 'Invalid credentials.'); 51 | 52 | // Denied - Can't login with invalid password. 53 | $this->client->request('GET', '/login'); 54 | self::assertResponseIsSuccessful(); 55 | 56 | $this->client->submitForm('Sign in', [ 57 | '_username' => 'email@example.com', 58 | '_password' => 'bad-password', 59 | ]); 60 | 61 | self::assertResponseRedirects('/login'); 62 | $this->client->followRedirect(); 63 | 64 | // Ensure we do not reveal the user exists but the password is wrong. 65 | self::assertSelectorTextContains('.alert-danger', 'Invalid credentials.'); 66 | 67 | // Success - Login with valid credentials is allowed. 68 | $this->client->submitForm('Sign in', [ 69 | '_username' => 'email@example.com', 70 | '_password' => 'password', 71 | ]); 72 | 73 | self::assertResponseRedirects('/'); 74 | $this->client->followRedirect(); 75 | 76 | self::assertSelectorNotExists('.alert-danger'); 77 | self::assertResponseIsSuccessful(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /templates/security/formLogin/login_form.tpl.php: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block title %}Log in!{% endblock %} 4 | 5 | {% block body %} 6 |
7 | {% if error %} 8 |
{{ error.messageKey|trans(error.messageData, 'security') }}
9 | {% endif %} 10 | 11 | 12 | {% if app.user %} 13 |
14 | You are logged in as {{ app.user.userIdentifier }}, Logout 15 |
16 | {% endif %} 17 | 18 | 19 |

Please sign in

20 | 21 | 22 | 23 | 24 | 25 | 26 | {# 27 | Uncomment this section and add a remember_me option below your firewall to activate remember me functionality. 28 | See https://symfony.com/doc/current/security/remember_me.html 29 | 30 |
31 | 32 | 33 |
34 | #} 35 | 36 | 39 |
40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /templates/serializer/Encoder.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | class implements EncoderInterface, DecoderInterface 8 | { 9 | public const FORMAT = ''; 10 | 11 | public function encode(mixed $data, string $format, array $context = []): string 12 | { 13 | // TODO: return your encoded data 14 | return ''; 15 | } 16 | 17 | public function supportsEncoding(string $format): bool 18 | { 19 | return self::FORMAT === $format; 20 | } 21 | 22 | public function decode(string $data, string $format, array $context = []): mixed 23 | { 24 | // TODO: return your decoded data 25 | return ''; 26 | } 27 | 28 | public function supportsDecoding(string $format): bool 29 | { 30 | return self::FORMAT === $format; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /templates/serializer/Normalizer.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | class implements NormalizerInterface 8 | { 9 | public function __construct( 10 | #[Autowire(service: 'serializer.normalizer.object')] 11 | private NormalizerInterface $normalizer 12 | ) { 13 | } 14 | 15 | public function normalize($object, ?string $format = null, array $context = []): array 16 | { 17 | $data = $this->normalizer->normalize($object, $format, $context); 18 | 19 | // TODO: add, edit, or delete some data 20 | 21 | return $data; 22 | } 23 | 24 | public function supportsNormalization($data, ?string $format = null, array $context = []): bool 25 | { 26 | 27 | return $data instanceof ; 28 | 29 | // TODO: return $data instanceof Object 30 | 31 | } 32 | 33 | public function getSupportedTypes(?string $format): array 34 | { 35 | 36 | return [::class => true]; 37 | 38 | // TODO: return [Object::class => true]; 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /templates/stimulus/Controller.tpl.php: -------------------------------------------------------------------------------- 1 | import { Controller } from '@hotwired/stimulus'; 2 | 3 | /* 4 | * The following line makes this controller "lazy": it won't be downloaded until needed 5 | * See https://symfony.com/bundles/StimulusBundle/current/index.html#lazy-stimulus-controllers 6 | */ 7 | 8 | /* stimulusFetch: 'lazy' */ 9 | export default class extends Controller { 10 | 11 | 12 | static values = { 13 | 14 | : , 15 | 16 | } 17 | 18 | 19 | 20 | initialize() { 21 | // Called once when the controller is first instantiated (per element) 22 | 23 | // Here you can initialize variables, create scoped callables for event 24 | // listeners, instantiate external libraries, etc. 25 | // this._fooBar = this.fooBar.bind(this) 26 | } 27 | 28 | connect() { 29 | // Called every time the controller is connected to the DOM 30 | // (on page load, when it's added to the DOM, moved in the DOM, etc.) 31 | 32 | // Here you can add event listeners on the element or target elements, 33 | // add or remove classes, attributes, dispatch custom events, etc. 34 | // this.fooTarget.addEventListener('click', this._fooBar) 35 | } 36 | 37 | // Add custom controller actions here 38 | // fooBar() { this.fooTarget.classList.toggle(this.bazClass) } 39 | 40 | disconnect() { 41 | // Called anytime its element is disconnected from the DOM 42 | // (on page change, when it's removed from or moved in the DOM, etc.) 43 | 44 | // Here you should remove all event listeners added in "connect()" 45 | // this.fooTarget.removeEventListener('click', this._fooBar) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /templates/test/ApiTestCase.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | use ; 6 | 7 | class extends ApiTestCase 8 | { 9 | public function testSomething(): void 10 | { 11 | $response = static::createClient()->request('GET', '/'); 12 | 13 | $this->assertResponseIsSuccessful(); 14 | $this->assertJsonContains(['@id' => '/']); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /templates/test/Functional.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | namespace ; 5 | 6 | 7 | 8 | class extends 9 | { 10 | public function testSomething(): void 11 | { 12 | 13 | $client = static::createPantherClient(); 14 | 15 | $client = static::createClient(); 16 | 17 | $crawler = $client->request('GET', '/'); 18 | 19 | 20 | 21 | $this->assertResponseIsSuccessful(); 22 | 23 | $this->assertSelectorTextContains('h1', 'Hello World'); 24 | 25 | 26 | $this->assertSame(200, $client->getResponse()->getStatusCode()); 27 | 28 | $this->assertStringContainsString('Hello World', $crawler->filter('h1')->text()); 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /templates/test/KernelTestCase.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; 6 | 7 | class extends KernelTestCase 8 | { 9 | public function testSomething(): void 10 | { 11 | $kernel = self::bootKernel(); 12 | 13 | $this->assertSame('test', $kernel->getEnvironment()); 14 | // $routerService = static::getContainer()->get('router'); 15 | // $myCustomService = static::getContainer()->get(CustomService::class); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /templates/test/PantherTestCase.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | use Symfony\Component\Panther\PantherTestCase; 6 | 7 | class extends PantherTestCase 8 | { 9 | public function testSomething(): void 10 | { 11 | $client = static::createPantherClient(); 12 | $crawler = $client->request('GET', '/'); 13 | 14 | 15 | $this->assertSelectorTextContains('h1', 'Hello World'); 16 | 17 | $this->assertStringContainsString('Hello World', $crawler->filter('h1')->text()); 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /templates/test/TestCase.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | use PHPUnit\Framework\TestCase; 6 | 7 | class extends TestCase 8 | { 9 | public function testSomething(): void 10 | { 11 | $this->assertTrue(true); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /templates/test/Unit.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | namespace ; 5 | 6 | 7 | 8 | class extends TestCase 9 | { 10 | public function testSomething(): void 11 | { 12 | $this->assertTrue(true); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /templates/test/WebTestCase.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; 6 | 7 | class extends WebTestCase 8 | { 9 | public function testSomething(): void 10 | { 11 | $client = static::createClient(); 12 | $crawler = $client->request('GET', '/'); 13 | 14 | 15 | $this->assertResponseIsSuccessful(); 16 | $this->assertSelectorTextContains('h1', 'Hello World'); 17 | 18 | $this->assertSame(200, $client->getResponse()->getStatusCode()); 19 | $this->assertStringContainsString('Hello World', $crawler->filter('h1')->text()); 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /templates/twig/Component.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; 6 | 7 | #[AsTwigComponent] 8 | final class 9 | { 10 | } 11 | -------------------------------------------------------------------------------- /templates/twig/Extension.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | class extends AbstractExtension 8 | { 9 | public function getFilters(): array 10 | { 11 | return [ 12 | // If your filter generates SAFE HTML, you should add a third 13 | // parameter: ['is_safe' => ['html']] 14 | // Reference: https://twig.symfony.com/doc/3.x/advanced.html#automatic-escaping 15 | new TwigFilter('filter_name', [::class, 'doSomething']), 16 | ]; 17 | } 18 | 19 | public function getFunctions(): array 20 | { 21 | return [ 22 | new TwigFunction('function_name', [::class, 'doSomething']), 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /templates/twig/LiveComponent.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; 6 | use Symfony\UX\LiveComponent\DefaultActionTrait; 7 | 8 | #[AsLiveComponent] 9 | final class 10 | { 11 | use DefaultActionTrait; 12 | } 13 | -------------------------------------------------------------------------------- /templates/twig/Runtime.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | class implements RuntimeExtensionInterface 8 | { 9 | public function __construct() 10 | { 11 | // Inject dependencies if needed 12 | } 13 | 14 | public function doSomething($value) 15 | { 16 | // ... 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /templates/twig/component_template.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /templates/validator/Constraint.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace getNamespace(); ?>; 4 | 5 | getUseStatements(); ?> 6 | 7 | #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] 8 | getClassDeclaration(); ?> 9 | 10 | { 11 | public string $message = 'The string "{{ value }}" contains an illegal character: it can only contain letters or numbers.'; 12 | 13 | // You can use #[HasNamedArguments] to make some constraint options required. 14 | // All configurable options must be passed to the constructor. 15 | public function __construct( 16 | public string $mode = 'strict', 17 | ?array $groups = null, 18 | mixed $payload = null 19 | ) { 20 | parent::__construct([], $groups, $payload); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /templates/validator/Validator.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace getNamespace(); ?>; 4 | 5 | getUseStatements(); ?> 6 | 7 | getClassDeclaration(); ?> 8 | 9 | { 10 | public function validate(mixed $value, Constraint $constraint): void 11 | { 12 | /** @var $constraint */ 13 | 14 | if (null === $value || '' === $value) { 15 | return; 16 | } 17 | 18 | // TODO: implement the validation here 19 | $this->context->buildViolation($constraint->message) 20 | ->setParameter('{{ value }}', $value) 21 | ->addViolation() 22 | ; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /templates/verifyEmail/EmailVerifier.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | class 8 | { 9 | public function __construct( 10 | private VerifyEmailHelperInterface $verifyEmailHelper, 11 | private MailerInterface $mailer, 12 | private EntityManagerInterface $entityManager 13 | ) { 14 | } 15 | 16 | public function sendEmailConfirmation(string $verifyEmailRouteName, $user, TemplatedEmail $email): void 17 | { 18 | $signatureComponents = $this->verifyEmailHelper->generateSignature( 19 | $verifyEmailRouteName, 20 | (string) $user->(), 21 | 22 | (string) $user->(), 23 | ['id' => $user->()] 24 | 25 | (string) $user->() 26 | 27 | ); 28 | 29 | $context = $email->getContext(); 30 | $context['signedUrl'] = $signatureComponents->getSignedUrl(); 31 | $context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey(); 32 | $context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData(); 33 | 34 | $email->context($context); 35 | 36 | $this->mailer->send($email); 37 | } 38 | 39 | /** 40 | * @throws VerifyEmailExceptionInterface 41 | */ 42 | public function handleEmailConfirmation(Request $request, $user): void 43 | { 44 | $this->verifyEmailHelper->validateEmailConfirmationFromRequest($request, (string) $user->(), (string) $user->()); 45 | 46 | $user->setIsVerified(true); 47 | 48 | $this->entityManager->persist($user); 49 | $this->entityManager->flush(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /templates/webhook/RequestParser.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | 6 | 7 | final class extends AbstractRequestParser 8 | { 9 | protected function getRequestMatcher(): RequestMatcherInterface 10 | { 11 | 12 | return new ChainRequestMatcher([ 13 | 14 | 15 | 16 | new (), 17 | 18 | ]); 19 | 20 | return new (); 21 | 22 | } 23 | 24 | /** 25 | * @throws JsonException 26 | */ 27 | protected function doParse(Request $request, #[\SensitiveParameter] string $secret): ?RemoteEvent 28 | { 29 | // TODO: Adapt or replace the content of this method to fit your need. 30 | 31 | // Validate the request against $secret. 32 | $authToken = $request->headers->get('X-Authentication-Token'); 33 | if ($authToken !== $secret) { 34 | throw new RejectWebhookException(Response::HTTP_UNAUTHORIZED, 'Invalid authentication token.'); 35 | } 36 | 37 | // Validate the request payload. 38 | if (!$request->getPayload()->has('name') 39 | || !$request->getPayload()->has('id')) { 40 | throw new RejectWebhookException(Response::HTTP_BAD_REQUEST, 'Request payload does not contain required fields.'); 41 | } 42 | 43 | // Parse the request payload and return a RemoteEvent object. 44 | $payload = $request->getPayload(); 45 | 46 | return new RemoteEvent( 47 | $payload->getString('name'), 48 | $payload->getString('id'), 49 | $payload->all(), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /templates/webhook/WebhookConsumer.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace ; 4 | 5 | use Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer; 6 | use Symfony\Component\RemoteEvent\Consumer\ConsumerInterface; 7 | use Symfony\Component\RemoteEvent\RemoteEvent; 8 | 9 | #[AsRemoteEventConsumer('')] 10 | final class implements ConsumerInterface 11 | { 12 | public function __construct() 13 | { 14 | } 15 | 16 | public function consume(RemoteEvent $event): void 17 | { 18 | // Implement your own logic here 19 | } 20 | } 21 | --------------------------------------------------------------------------------