diff --git a/config/makers.xml b/config/makers.xml index ad7d45483..e8f5ac1ac 100644 --- a/config/makers.xml +++ b/config/makers.xml @@ -27,6 +27,7 @@ + @@ -82,6 +83,7 @@ + @@ -90,6 +92,7 @@ + @@ -118,6 +121,7 @@ + @@ -157,6 +161,7 @@ + diff --git a/config/services.xml b/config/services.xml index 5fbc0439b..d52e9a696 100644 --- a/config/services.xml +++ b/config/services.xml @@ -16,7 +16,7 @@ - + @@ -36,10 +36,14 @@ - + + + + + %env(default::string:MAKER_PHP_CS_FIXER_BINARY_PATH)% %env(default::string:MAKER_PHP_CS_FIXER_CONFIG_PATH)% @@ -54,7 +58,7 @@ - + null @@ -82,7 +86,7 @@ - + diff --git a/src/Doctrine/DoctrineHelper.php b/src/Doctrine/DoctrineHelper.php index 21b4005fa..2c70e8dd3 100644 --- a/src/Doctrine/DoctrineHelper.php +++ b/src/Doctrine/DoctrineHelper.php @@ -26,6 +26,7 @@ use Doctrine\Persistence\Mapping\MappingException as PersistenceMappingException; use Doctrine\Persistence\Mapping\StaticReflectionService; use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; +use Symfony\Bundle\MakerBundle\Util\NamespacesHelper; use Symfony\Component\Uid\Ulid; use Symfony\Component\Uid\Uuid; @@ -39,11 +40,10 @@ final class DoctrineHelper { public function __construct( - private string $entityNamespace, + private NamespacesHelper $namespacesHelper, private ?ManagerRegistry $registry = null, private ?array $mappingDriversByPrefix = null, ) { - $this->entityNamespace = trim($entityNamespace, '\\'); } public function getRegistry(): ManagerRegistry @@ -64,7 +64,11 @@ private function isDoctrineInstalled(): bool public function getEntityNamespace(): string { - return $this->entityNamespace; + return \sprintf( + '%s\\%s', + $this->namespacesHelper->getRootNamespace(), + $this->namespacesHelper->getEntityNamespace() + ); } public function doesClassUseDriver(string $className, string $driverClass): bool @@ -138,7 +142,7 @@ public function getEntitiesForAutocomplete(): array $allMetadata = $this->getMetadata(); foreach (array_keys($allMetadata) as $classname) { - $entityClassDetails = new ClassNameDetails($classname, $this->entityNamespace); + $entityClassDetails = new ClassNameDetails($classname, $this->getEntityNamespace()); $entities[] = $entityClassDetails->getRelativeName(); } } diff --git a/src/Doctrine/EntityClassGenerator.php b/src/Doctrine/EntityClassGenerator.php index a54790b77..f357ceb8d 100644 --- a/src/Doctrine/EntityClassGenerator.php +++ b/src/Doctrine/EntityClassGenerator.php @@ -44,7 +44,7 @@ public function generateEntityClass(ClassNameDetails $entityClassDetails, bool $ { $repoClassDetails = $this->generator->createClassNameDetails( $entityClassDetails->getRelativeName(), - 'Repository\\', + $this->generator->getNamespacesHelper()->getRepositoryNamespace(), 'Repository' ); diff --git a/src/Generator.php b/src/Generator.php index fbadc1bca..6062ff894 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -15,6 +15,7 @@ use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException; use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData; +use Symfony\Bundle\MakerBundle\Util\NamespacesHelper; use Symfony\Bundle\MakerBundle\Util\PhpCompatUtil; use Symfony\Bundle\MakerBundle\Util\TemplateComponentGenerator; @@ -30,12 +31,11 @@ class Generator public function __construct( private FileManager $fileManager, - private string $namespacePrefix, + private NamespacesHelper $namespacesHelper, ?PhpCompatUtil $phpCompatUtil = null, private ?TemplateComponentGenerator $templateComponentGenerator = null, ) { $this->twigHelper = new GeneratorTwigHelper($fileManager); - $this->namespacePrefix = trim($namespacePrefix, '\\'); if (null !== $phpCompatUtil) { trigger_deprecation('symfony/maker-bundle', 'v1.44.0', 'Initializing Generator while providing an instance of PhpCompatUtil is deprecated.'); @@ -180,7 +180,7 @@ public function getFileContentsForPendingOperation(string $targetPath): string */ public function createClassNameDetails(string $name, string $namespacePrefix, string $suffix = '', string $validationErrorMessage = ''): ClassNameDetails { - $fullNamespacePrefix = $this->namespacePrefix.'\\'.$namespacePrefix; + $fullNamespacePrefix = $this->namespacesHelper->getRootNamespace().'\\'.$namespacePrefix; if ('\\' === $name[0]) { // class is already "absolute" - leave it alone (but strip opening \) $className = substr($name, 1); @@ -240,9 +240,14 @@ public function writeChanges() $this->pendingOperations = []; } + public function getNamespacesHelper(): NamespacesHelper + { + return $this->namespacesHelper; + } + public function getRootNamespace(): string { - return $this->namespacePrefix; + return $this->namespacesHelper->getRootNamespace(); } public function generateController(string $controllerClassName, string $controllerTemplatePath, array $parameters = []): string diff --git a/src/Maker/MakeAuthenticator.php b/src/Maker/MakeAuthenticator.php index 3f7c9e417..05eb8ae10 100644 --- a/src/Maker/MakeAuthenticator.php +++ b/src/Maker/MakeAuthenticator.php @@ -151,7 +151,7 @@ function ($answer) { ); $input->setArgument('authenticator-class', $io->askQuestion($questionAuthenticatorClass)); - $interactiveSecurityHelper = new InteractiveSecurityHelper(); + $interactiveSecurityHelper = new InteractiveSecurityHelper($this->generator->getNamespacesHelper()); $command->addOption('firewall-name', null, InputOption::VALUE_OPTIONAL); $input->setOption('firewall-name', $interactiveSecurityHelper->guessFirewallName($io, $securityData)); diff --git a/src/Maker/MakeCommand.php b/src/Maker/MakeCommand.php index 52d331c53..dca98bd9d 100644 --- a/src/Maker/MakeCommand.php +++ b/src/Maker/MakeCommand.php @@ -69,7 +69,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen $commandClassNameDetails = $generator->createClassNameDetails( $commandNameHasAppPrefix ? substr($commandName, 4) : $commandName, - 'Command\\', + $generator->getNamespacesHelper()->getCommandNamespace(), 'Command', \sprintf('The "%s" command name is not valid because it would be implemented by "%s" class, which is not valid as a PHP class name (it must start with a letter or underscore, followed by any number of letters, numbers, or underscores).', $commandName, Str::asClassName($commandName, 'Command')) ); diff --git a/src/Maker/MakeController.php b/src/Maker/MakeController.php index 58e049eb5..18f156cd8 100644 --- a/src/Maker/MakeController.php +++ b/src/Maker/MakeController.php @@ -20,6 +20,7 @@ use Symfony\Bundle\MakerBundle\Maker\Common\CanGenerateTestsTrait; use Symfony\Bundle\MakerBundle\Str; use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData; +use Symfony\Bundle\MakerBundle\Util\NamespacesHelper; use Symfony\Bundle\MakerBundle\Util\PhpCompatUtil; use Symfony\Bundle\TwigBundle\TwigBundle; use Symfony\Component\Console\Command\Command; @@ -43,8 +44,10 @@ final class MakeController extends AbstractMaker private bool $usesTwigTemplate; private string $twigTemplatePath; - public function __construct(private ?PhpCompatUtil $phpCompatUtil = null) - { + public function __construct( + private NamespacesHelper $namespacesHelper, + private ?PhpCompatUtil $phpCompatUtil = null, + ) { if (null !== $phpCompatUtil) { @trigger_deprecation( 'symfony/maker-bundle', @@ -82,7 +85,7 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma $this->isInvokable = (bool) $input->getOption('invokable'); $controllerClass = $input->getArgument('controller-class'); - $controllerClassName = \sprintf('Controller\%s', $controllerClass); + $controllerClassName = \sprintf('%s\%s', $this->namespacesHelper->getControllerNamespace(), $controllerClass); // If the class name provided is absolute, we do not assume it will live in src/Controller // e.g. src/Custom/Location/For/MyController instead of src/Controller/MyController @@ -92,6 +95,7 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma $this->controllerClassData = ClassData::create( class: $controllerClassName, + rootNamespace: $this->namespacesHelper->getRootNamespace(), suffix: 'Controller', extendsClass: AbstractController::class, useStatements: [ @@ -138,8 +142,15 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen } if ($this->shouldGenerateTests()) { + $testClassName = \sprintf( + '%s\%s\%s', + $this->namespacesHelper->getTestNamespace(), + $this->namespacesHelper->getControllerNamespace(), + $this->controllerClassData->getClassName(relative: true, withoutSuffix: true) + ); $testClassData = ClassData::create( - class: \sprintf('Tests\Controller\%s', $this->controllerClassData->getClassName(relative: true, withoutSuffix: true)), + class: $testClassName, + rootNamespace: $this->namespacesHelper->getRootNamespace(), suffix: 'ControllerTest', extendsClass: WebTestCase::class, ); diff --git a/src/Maker/MakeCrud.php b/src/Maker/MakeCrud.php index f535d24bd..670ecc9f6 100644 --- a/src/Maker/MakeCrud.php +++ b/src/Maker/MakeCrud.php @@ -107,7 +107,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen { $entityClassDetails = $generator->createClassNameDetails( Validator::entityExists($input->getArgument('entity-class'), $this->doctrineHelper->getEntitiesForAutocomplete()), - 'Entity\\' + $generator->getNamespacesHelper()->getEntityNamespace() ); $entityDoctrineDetails = $this->doctrineHelper->createDoctrineDetails($entityClassDetails->getFullName()); @@ -118,7 +118,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen if (null !== $entityDoctrineDetails->getRepositoryClass()) { $repositoryClassDetails = $generator->createClassNameDetails( '\\'.$entityDoctrineDetails->getRepositoryClass(), - 'Repository\\', + $generator->getNamespacesHelper()->getRepositoryNamespace(), 'Repository' ); @@ -133,7 +133,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen $controllerClassDetails = $generator->createClassNameDetails( $this->controllerClassName, - 'Controller\\', + $generator->getNamespacesHelper()->getControllerNamespace(), 'Controller' ); @@ -141,14 +141,15 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen do { $formClassDetails = $generator->createClassNameDetails( $entityClassDetails->getRelativeNameWithoutSuffix().($iter ?: '').'Type', - 'Form\\', + $generator->getNamespacesHelper()->getFormNamespace(), 'Type' ); ++$iter; } while (class_exists($formClassDetails->getFullName())); $controllerClassData = ClassData::create( - class: \sprintf('Controller\%s', $this->controllerClassName), + class: \sprintf('%s\%s', $generator->getNamespacesHelper()->getControllerNamespace(), $this->controllerClassName), + rootNamespace: $generator->getRootNamespace(), suffix: 'Controller', extendsClass: AbstractController::class, useStatements: [ @@ -247,8 +248,15 @@ class: \sprintf('Controller\%s', $this->controllerClassName), } if ($this->shouldGenerateTests()) { + $testClassName = \sprintf( + '%s\%s\%s', + $generator->getNamespacesHelper()->getTestNamespace(), + $generator->getNamespacesHelper()->getControllerNamespace(), + $entityClassDetails->getRelativeNameWithoutSuffix() + ); $testClassData = ClassData::create( - class: \sprintf('Tests\Controller\%s', $entityClassDetails->getRelativeNameWithoutSuffix()), + class: $testClassName, + rootNamespace: $generator->getRootNamespace(), suffix: 'ControllerTest', extendsClass: WebTestCase::class, useStatements: [ diff --git a/src/Maker/MakeEntity.php b/src/Maker/MakeEntity.php index e0498f36b..937a2b548 100644 --- a/src/Maker/MakeEntity.php +++ b/src/Maker/MakeEntity.php @@ -31,6 +31,7 @@ use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassProperty; use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; use Symfony\Bundle\MakerBundle\Util\CliOutputHelper; +use Symfony\Bundle\MakerBundle\Util\NamespacesHelper; use Symfony\Bundle\MakerBundle\Validator; use Symfony\Bundle\MercureBundle\DependencyInjection\MercureExtension; use Symfony\Component\Console\Command\Command; @@ -66,7 +67,7 @@ public function __construct( if (null === $generator) { @trigger_error(\sprintf('Passing a "%s" instance as 4th argument is mandatory since version 1.5.', Generator::class), \E_USER_DEPRECATED); - $this->generator = new Generator($fileManager, 'App\\'); + $this->generator = new Generator($fileManager, new NamespacesHelper()); } else { $this->generator = $generator; } @@ -142,7 +143,7 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma if ( !$input->getOption('api-resource') && class_exists(ApiResource::class) - && !class_exists($this->generator->createClassNameDetails($entityClassName, 'Entity\\')->getFullName()) + && !class_exists($this->generator->createClassNameDetails($entityClassName, $this->generator->getNamespacesHelper()->getEntityNamespace())->getFullName()) ) { $description = $command->getDefinition()->getOption('api-resource')->getDescription(); $question = new ConfirmationQuestion($description, false); @@ -154,7 +155,7 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma if ( !$input->getOption('broadcast') && class_exists(Broadcast::class) - && !class_exists($this->generator->createClassNameDetails($entityClassName, 'Entity\\')->getFullName()) + && !class_exists($this->generator->createClassNameDetails($entityClassName, $this->generator->getNamespacesHelper()->getEntityNamespace())->getFullName()) ) { $description = $command->getDefinition()->getOption('broadcast')->getDescription(); $question = new ConfirmationQuestion($description, false); @@ -183,7 +184,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen $entityClassDetails = $generator->createClassNameDetails( $input->getArgument('name'), - 'Entity\\' + $generator->getNamespacesHelper()->getEntityNamespace(), ); $classExists = class_exists($entityClassDetails->getFullName()); diff --git a/src/Maker/MakeFixtures.php b/src/Maker/MakeFixtures.php index 137ef5c98..02e67f02c 100644 --- a/src/Maker/MakeFixtures.php +++ b/src/Maker/MakeFixtures.php @@ -53,7 +53,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen { $fixturesClassNameDetails = $generator->createClassNameDetails( $input->getArgument('fixtures-class'), - 'DataFixtures\\' + $generator->getNamespacesHelper()->getFixturesNamespace() ); $useStatements = new UseStatementGenerator([ diff --git a/src/Maker/MakeForm.php b/src/Maker/MakeForm.php index 7387ead57..6c693d946 100644 --- a/src/Maker/MakeForm.php +++ b/src/Maker/MakeForm.php @@ -79,7 +79,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen { $formClassNameDetails = $generator->createClassNameDetails( $input->getArgument('name'), - 'Form\\', + $generator->getNamespacesHelper()->getFormNamespace(), 'Type' ); @@ -91,7 +91,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen if (null !== $boundClass) { $boundClassDetails = $generator->createClassNameDetails( $boundClass, - 'Entity\\' + $generator->getNamespacesHelper()->getEntityNamespace() ); $doctrineEntityDetails = $this->entityHelper->createDoctrineDetails($boundClassDetails->getFullName()); diff --git a/src/Maker/MakeFunctionalTest.php b/src/Maker/MakeFunctionalTest.php index 16f0281c0..79e703d5e 100644 --- a/src/Maker/MakeFunctionalTest.php +++ b/src/Maker/MakeFunctionalTest.php @@ -58,7 +58,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen { $testClassNameDetails = $generator->createClassNameDetails( $input->getArgument('name'), - 'Tests\\', + $generator->getNamespacesHelper()->getTestNamespace(), 'Test' ); diff --git a/src/Maker/MakeListener.php b/src/Maker/MakeListener.php index 6b00b51f2..784616964 100644 --- a/src/Maker/MakeListener.php +++ b/src/Maker/MakeListener.php @@ -191,7 +191,7 @@ private function generateSubscriberClass(InputInterface $input, ConsoleStyle $io { $subscriberClassNameDetails = $generator->createClassNameDetails( $input->getArgument('name'), - 'EventSubscriber\\', + $generator->getNamespacesHelper()->getSubscriberNamespace(), 'Subscriber' ); @@ -220,7 +220,7 @@ private function generateListenerClass(InputInterface $input, ConsoleStyle $io, { $listenerClassNameDetails = $generator->createClassNameDetails( $input->getArgument('name'), - 'EventListener\\', + $generator->getNamespacesHelper()->getListenerNamespace(), 'Listener' ); diff --git a/src/Maker/MakeMessage.php b/src/Maker/MakeMessage.php index 74fc1e692..e7fdb8247 100644 --- a/src/Maker/MakeMessage.php +++ b/src/Maker/MakeMessage.php @@ -88,12 +88,12 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen { $messageClassNameDetails = $generator->createClassNameDetails( $input->getArgument('name'), - 'Message\\' + $generator->getNamespacesHelper()->getMessageNamespace() ); $handlerClassNameDetails = $generator->createClassNameDetails( $input->getArgument('name').'Handler', - 'MessageHandler\\', + $generator->getNamespacesHelper()->getMessageHandlerNamespace(), 'Handler' ); diff --git a/src/Maker/MakeMessengerMiddleware.php b/src/Maker/MakeMessengerMiddleware.php index f640d90cb..aba0f38da 100644 --- a/src/Maker/MakeMessengerMiddleware.php +++ b/src/Maker/MakeMessengerMiddleware.php @@ -53,7 +53,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen { $middlewareClassNameDetails = $generator->createClassNameDetails( $input->getArgument('name'), - 'Middleware\\', + $generator->getNamespacesHelper()->getMiddlewareNamespace(), 'Middleware' ); diff --git a/src/Maker/MakeRegistrationForm.php b/src/Maker/MakeRegistrationForm.php index 48a8ae34a..914a51e70 100644 --- a/src/Maker/MakeRegistrationForm.php +++ b/src/Maker/MakeRegistrationForm.php @@ -37,6 +37,7 @@ use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; use Symfony\Bundle\MakerBundle\Util\CliOutputHelper; +use Symfony\Bundle\MakerBundle\Util\NamespacesHelper; use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator; use Symfony\Bundle\MakerBundle\Validator; @@ -91,6 +92,7 @@ public function __construct( private FileManager $fileManager, private FormTypeRenderer $formTypeRenderer, private DoctrineHelper $doctrineHelper, + private NamespacesHelper $namespacesHelper, private ?RouterInterface $router = null, ) { } @@ -116,7 +118,7 @@ public function configureCommand(Command $command, InputConfiguration $inputConf public function interact(InputInterface $input, ConsoleStyle $io, Command $command): void { - $interactiveSecurityHelper = new InteractiveSecurityHelper(); + $interactiveSecurityHelper = new InteractiveSecurityHelper($this->namespacesHelper); if (null === $this->router) { throw new RuntimeCommandException('Router have been explicitly disabled in your configuration. This command needs to use the router.'); @@ -215,7 +217,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen { $userClassNameDetails = $generator->createClassNameDetails( '\\'.$this->userClass, - 'Entity\\' + $generator->getNamespacesHelper()->getEntityNamespace() ); $userDoctrineDetails = $this->doctrineHelper->createDoctrineDetails($userClassNameDetails->getFullName()); @@ -229,7 +231,11 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen $userRepository = $userDoctrineDetails->getRepositoryClass(); if (null !== $userRepository) { - $userRepoClassDetails = $generator->createClassNameDetails('\\'.$userRepository, 'Repository\\', 'Repository'); + $userRepoClassDetails = $generator->createClassNameDetails( + '\\'.$userRepository, + $generator->getNamespacesHelper()->getRepositoryNamespace(), + 'Repository' + ); $userRepoVars = [ 'repository_full_class_name' => $userRepoClassDetails->getFullName(), @@ -240,7 +246,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen $verifyEmailServiceClassNameDetails = $generator->createClassNameDetails( 'EmailVerifier', - 'Security\\' + $generator->getNamespacesHelper()->getSecurityNamespace() ); $verifyEmailVars = ['will_verify_email' => $this->willVerifyEmail]; @@ -297,7 +303,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen // 2) Generate the controller $controllerClassNameDetails = $generator->createClassNameDetails( 'RegistrationController', - 'Controller\\' + $generator->getNamespacesHelper()->getControllerNamespace() ); $useStatements = new UseStatementGenerator([ @@ -417,7 +423,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen if ($this->shouldGenerateTests()) { $testClassDetails = $generator->createClassNameDetails( 'RegistrationControllerTest', - 'Test\\' + $generator->getNamespacesHelper()->getTestNamespace() ); $useStatements = new UseStatementGenerator([ @@ -549,7 +555,7 @@ private function generateFormClass(ClassNameDetails $userClassDetails, Generator { $formClassDetails = $generator->createClassNameDetails( 'RegistrationFormType', - 'Form\\' + $generator->getNamespacesHelper()->getFormNamespace() ); $formFields = [ diff --git a/src/Maker/MakeResetPassword.php b/src/Maker/MakeResetPassword.php index ae7b039c7..b20759c81 100644 --- a/src/Maker/MakeResetPassword.php +++ b/src/Maker/MakeResetPassword.php @@ -34,6 +34,7 @@ use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; use Symfony\Bundle\MakerBundle\Util\CliOutputHelper; +use Symfony\Bundle\MakerBundle\Util\NamespacesHelper; use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator; use Symfony\Bundle\MakerBundle\Validator; @@ -102,6 +103,7 @@ public function __construct( private FileManager $fileManager, private DoctrineHelper $doctrineHelper, private EntityClassGenerator $entityClassGenerator, + private NamespacesHelper $namespacesHelper, private ?RouterInterface $router = null, ) { } @@ -150,7 +152,7 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma $this->checkIsUsingUid($input); - $interactiveSecurityHelper = new InteractiveSecurityHelper(); + $interactiveSecurityHelper = new InteractiveSecurityHelper($this->namespacesHelper); if (!$this->fileManager->fileExists($path = 'config/packages/security.yaml')) { throw new RuntimeCommandException('The file "config/packages/security.yaml" does not exist. PHP & XML configuration formats are currently not supported.'); @@ -160,12 +162,13 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma $securityData = $manipulator->getData(); $providersData = $securityData['security']['providers'] ?? []; - $this->userClass = $interactiveSecurityHelper->guessUserClass( - $io, - $providersData, - 'What is the User entity that should be used with the "forgotten password" feature? (e.g. App\\Entity\\User)' + $questionText = \sprintf( + 'What is the User entity that should be used with the "forgotten password" feature? (e.g. %s\\%s\\User)', + $this->namespacesHelper->getRootNamespace(), + $this->namespacesHelper->getEntityNamespace() ); + $this->userClass = $interactiveSecurityHelper->guessUserClass($io, $providersData, $questionText); $this->emailPropertyName = $interactiveSecurityHelper->guessEmailField($io, $this->userClass); $this->emailGetterMethodName = $interactiveSecurityHelper->guessEmailGetter($io, $this->userClass, $this->emailPropertyName); $this->passwordSetterMethodName = $interactiveSecurityHelper->guessPasswordSetter($io, $this->userClass); @@ -208,32 +211,32 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen { $userClassNameDetails = $generator->createClassNameDetails( '\\'.$this->userClass, - 'Entity\\' + $generator->getNamespacesHelper()->getEntityNamespace() ); $controllerClassNameDetails = $generator->createClassNameDetails( 'ResetPasswordController', - 'Controller\\' + $generator->getNamespacesHelper()->getControllerNamespace() ); $requestClassNameDetails = $generator->createClassNameDetails( 'ResetPasswordRequest', - 'Entity\\' + $generator->getNamespacesHelper()->getEntityNamespace() ); $repositoryClassNameDetails = $generator->createClassNameDetails( 'ResetPasswordRequestRepository', - 'Repository\\' + $generator->getNamespacesHelper()->getRepositoryNamespace() ); $requestFormTypeClassNameDetails = $generator->createClassNameDetails( 'ResetPasswordRequestFormType', - 'Form\\' + $generator->getNamespacesHelper()->getFormNamespace() ); $changePasswordFormTypeClassNameDetails = $generator->createClassNameDetails( 'ChangePasswordFormType', - 'Form\\' + $generator->getNamespacesHelper()->getFormNamespace() ); $useStatements = new UseStatementGenerator([ @@ -353,12 +356,12 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen if ($this->shouldGenerateTests()) { $testClassDetails = $generator->createClassNameDetails( 'ResetPasswordControllerTest', - 'Test\\', + $generator->getNamespacesHelper()->getTestNamespace(), ); $userRepositoryDetails = $generator->createClassNameDetails( \sprintf('%sRepository', $userClassNameDetails->getShortName()), - 'Repository\\' + $generator->getNamespacesHelper()->getRepositoryNamespace() ); $useStatements = new UseStatementGenerator([ diff --git a/src/Maker/MakeSchedule.php b/src/Maker/MakeSchedule.php index ff1c94ffd..1f511cab6 100644 --- a/src/Maker/MakeSchedule.php +++ b/src/Maker/MakeSchedule.php @@ -107,7 +107,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen { $scheduleClassDetails = $generator->createClassNameDetails( $this->scheduleName, - 'Scheduler\\', + $generator->getNamespacesHelper()->getSchedulerNamespace(), ); $useStatements = new UseStatementGenerator([ @@ -119,7 +119,14 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen ]); if (null !== $this->message) { - $useStatements->addUseStatement('App\\Message\\'.$this->message); + $messageClass = \sprintf( + '%s\\%s\\%s', + $generator->getRootNamespace(), + $generator->getNamespacesHelper()->getMessageNamespace(), + $this->message + ); + + $useStatements->addUseStatement($messageClass); } $generator->generateClass( diff --git a/src/Maker/MakeSerializerEncoder.php b/src/Maker/MakeSerializerEncoder.php index 8fc033b6b..285f9b381 100644 --- a/src/Maker/MakeSerializerEncoder.php +++ b/src/Maker/MakeSerializerEncoder.php @@ -52,7 +52,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen { $encoderClassNameDetails = $generator->createClassNameDetails( $input->getArgument('name'), - 'Serializer\\', + $generator->getNamespacesHelper()->getSerializerNamespace(), 'Encoder' ); $format = $input->getArgument('format'); diff --git a/src/Maker/MakeSerializerNormalizer.php b/src/Maker/MakeSerializerNormalizer.php index 39381ece1..db4e14ef9 100644 --- a/src/Maker/MakeSerializerNormalizer.php +++ b/src/Maker/MakeSerializerNormalizer.php @@ -62,19 +62,24 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen { $normalizerClassNameDetails = $generator->createClassNameDetails( $input->getArgument('name'), - 'Serializer\\Normalizer\\', + $generator->getNamespacesHelper()->getSerializerNamespace().'\\Normalizer\\', \Normalizer::class ); $useStatements = new UseStatementGenerator([ NormalizerInterface::class, Autowire::class, - \sprintf('App\Entity\%s', str_replace('Normalizer', '', $normalizerClassNameDetails->getShortName())), + \sprintf( + '%s\%s\%s', + $generator->getRootNamespace(), + $generator->getNamespacesHelper()->getEntityNamespace(), + str_replace('Normalizer', '', $normalizerClassNameDetails->getShortName()) + ), ]); $entityDetails = $generator->createClassNameDetails( str_replace('Normalizer', '', $normalizerClassNameDetails->getShortName()), - 'Entity\\', + $generator->getNamespacesHelper()->getEntityNamespace(), ); if ($entityExists = class_exists($entityDetails->getFullName())) { diff --git a/src/Maker/MakeSubscriber.php b/src/Maker/MakeSubscriber.php index cf1002c82..93760289c 100644 --- a/src/Maker/MakeSubscriber.php +++ b/src/Maker/MakeSubscriber.php @@ -80,7 +80,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen { $subscriberClassNameDetails = $generator->createClassNameDetails( $input->getArgument('name'), - 'EventSubscriber\\', + $generator->getNamespacesHelper()->getSubscriberNamespace(), 'Subscriber' ); diff --git a/src/Maker/MakeTest.php b/src/Maker/MakeTest.php index 47be59ab0..d43275b4c 100644 --- a/src/Maker/MakeTest.php +++ b/src/Maker/MakeTest.php @@ -19,6 +19,7 @@ use Symfony\Bundle\MakerBundle\Generator; use Symfony\Bundle\MakerBundle\InputAwareMakerInterface; use Symfony\Bundle\MakerBundle\InputConfiguration; +use Symfony\Bundle\MakerBundle\Util\NamespacesHelper; use Symfony\Bundle\MakerBundle\Validator; use Symfony\Component\BrowserKit\History; use Symfony\Component\Console\Command\Command; @@ -49,6 +50,10 @@ final class MakeTest extends AbstractMaker implements InputAwareMakerInterface 'PantherTestCase' => 'https://github.com/symfony/panther#testing-usage', ]; + public function __construct(private NamespacesHelper $namespacesHelper) + { + } + public static function getCommandName(): string { return 'make:test'; @@ -125,7 +130,11 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma 'Choose a class name for your test, like:', ' * UtilTest (to create tests/UtilTest.php)', ' * Service\\UtilTest (to create tests/Service/UtilTest.php)', - ' * \\App\Tests\\Service\\UtilTest (to create tests/Service/UtilTest.php)', + \sprintf( + ' * \\%s\\%s\\Service\\UtilTest (to create tests/Service/UtilTest.php)', + $this->namespacesHelper->getRootNamespace(), + $this->namespacesHelper->getTestNamespace() + ), ]); $nameArgument = $command->getDefinition()->getArgument('name'); @@ -138,7 +147,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen { $testClassNameDetails = $generator->createClassNameDetails( $input->getArgument('name'), - 'Tests\\', + $generator->getNamespacesHelper()->getTestNamespace(), 'Test' ); diff --git a/src/Maker/MakeTwigComponent.php b/src/Maker/MakeTwigComponent.php index 6c88b3bca..49ad13bf6 100644 --- a/src/Maker/MakeTwigComponent.php +++ b/src/Maker/MakeTwigComponent.php @@ -30,7 +30,7 @@ */ final class MakeTwigComponent extends AbstractMaker { - private string $namespace = 'Twig\\Components'; + private ?string $namespace = null; public function __construct(private FileManager $fileManager) { @@ -69,6 +69,8 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen throw new \RuntimeException('You must install symfony/ux-live-component to create a live component (composer require symfony/ux-live-component)'); } + $this->namespace ??= \sprintf('%s\\Components\\', $generator->getNamespacesHelper()->getTwigNamespace()); + $factory = $generator->createClassNameDetails( $name, str_replace($generator->getRootNamespace().'\\', '', $this->namespace), diff --git a/src/Maker/MakeTwigExtension.php b/src/Maker/MakeTwigExtension.php index 809b09259..fed6697a4 100644 --- a/src/Maker/MakeTwigExtension.php +++ b/src/Maker/MakeTwigExtension.php @@ -54,13 +54,13 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen $extensionClassNameDetails = $generator->createClassNameDetails( $name, - 'Twig\\Extension\\', + \sprintf('%s\\Extension\\', $generator->getNamespacesHelper()->getTwigNamespace()), 'Extension' ); $runtimeClassNameDetails = $generator->createClassNameDetails( $name, - 'Twig\\Runtime\\', + \sprintf('%s\\Runtime\\', $generator->getNamespacesHelper()->getTwigNamespace()), 'Runtime' ); diff --git a/src/Maker/MakeUnitTest.php b/src/Maker/MakeUnitTest.php index 79a407fbd..e39491fae 100644 --- a/src/Maker/MakeUnitTest.php +++ b/src/Maker/MakeUnitTest.php @@ -53,7 +53,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen { $testClassNameDetails = $generator->createClassNameDetails( $input->getArgument('name'), - 'Tests\\', + $generator->getNamespacesHelper()->getTestNamespace(), 'Test' ); diff --git a/src/Maker/MakeUser.php b/src/Maker/MakeUser.php index ddfd0721a..bc3f168e0 100644 --- a/src/Maker/MakeUser.php +++ b/src/Maker/MakeUser.php @@ -130,7 +130,9 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen $userClassNameDetails = $generator->createClassNameDetails( $input->getArgument('name'), - $userClassConfiguration->isEntity() ? 'Entity\\' : 'Security\\' + $userClassConfiguration->isEntity() + ? $generator->getNamespacesHelper()->getEntityNamespace() + : $generator->getNamespacesHelper()->getSecurityNamespace() ); // A) Generate the User class @@ -168,7 +170,11 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen // C) Generate a custom user provider, if necessary if (!$userClassConfiguration->isEntity()) { - $userClassConfiguration->setUserProviderClass($generator->getRootNamespace().'\\Security\\UserProvider'); + $userClassConfiguration->setUserProviderClass(\sprintf( + '%s\\%s\\UserProvider', + $generator->getRootNamespace(), + $generator->getNamespacesHelper()->getSecurityNamespace() + )); $useStatements = new UseStatementGenerator([ UnsupportedUserException::class, diff --git a/src/Maker/MakeValidator.php b/src/Maker/MakeValidator.php index f638a12d5..1c7b39506 100644 --- a/src/Maker/MakeValidator.php +++ b/src/Maker/MakeValidator.php @@ -53,7 +53,8 @@ public function configureCommand(Command $command, InputConfiguration $inputConf public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator) { $validatorClassData = ClassData::create( - class: \sprintf('Validator\\%s', $input->getArgument('name')), + class: \sprintf('%s\\%s', $generator->getNamespacesHelper()->getValidatorNamespace(), $input->getArgument('name')), + rootNamespace: $generator->getRootNamespace(), suffix: 'Validator', extendsClass: ConstraintValidator::class, useStatements: [ @@ -62,7 +63,8 @@ class: \sprintf('Validator\\%s', $input->getArgument('name')), ); $constraintDataClass = ClassData::create( - class: \sprintf('Validator\\%s', Str::removeSuffix($validatorClassData->getClassName(), 'Validator')), + class: \sprintf('%s\\%s', $generator->getNamespacesHelper()->getValidatorNamespace(), Str::removeSuffix($validatorClassData->getClassName(), 'Validator')), + rootNamespace: $generator->getRootNamespace(), extendsClass: Constraint::class, ); diff --git a/src/Maker/MakeVoter.php b/src/Maker/MakeVoter.php index 0e12f28f1..2f3f93a4f 100644 --- a/src/Maker/MakeVoter.php +++ b/src/Maker/MakeVoter.php @@ -50,7 +50,8 @@ public function configureCommand(Command $command, InputConfiguration $inputConf public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void { $voterClassData = ClassData::create( - class: \sprintf('Security\Voter\%s', $input->getArgument('name')), + class: \sprintf('%s\Voter\%s', $generator->getNamespacesHelper()->getSecurityNamespace(), $input->getArgument('name')), + rootNamespace: $generator->getRootNamespace(), suffix: 'Voter', extendsClass: Voter::class, useStatements: [ diff --git a/src/Maker/MakeWebhook.php b/src/Maker/MakeWebhook.php index a7b3f2423..2945746d7 100644 --- a/src/Maker/MakeWebhook.php +++ b/src/Maker/MakeWebhook.php @@ -148,11 +148,11 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen { $requestParserDetails = $this->generator->createClassNameDetails( Str::asClassName($this->name.'RequestParser'), - 'Webhook\\' + $generator->getNamespacesHelper()->getWebhookNamespace() ); $remoteEventConsumerDetails = $this->generator->createClassNameDetails( Str::asClassName($this->name.'WebhookConsumer'), - 'RemoteEvent\\' + $generator->getNamespacesHelper()->getRemoteEventNamespace() ); $this->addToYamlConfig($this->name, $requestParserDetails); diff --git a/src/Maker/Security/MakeFormLogin.php b/src/Maker/Security/MakeFormLogin.php index 641c048d9..03c7b35c9 100644 --- a/src/Maker/Security/MakeFormLogin.php +++ b/src/Maker/Security/MakeFormLogin.php @@ -29,6 +29,7 @@ use Symfony\Bundle\MakerBundle\Security\SecurityControllerBuilder; use Symfony\Bundle\MakerBundle\Str; use Symfony\Bundle\MakerBundle\Util\ClassSourceManipulator; +use Symfony\Bundle\MakerBundle\Util\NamespacesHelper; use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; use Symfony\Bundle\MakerBundle\Util\YamlSourceManipulator; use Symfony\Bundle\MakerBundle\Validator; @@ -67,6 +68,7 @@ public function __construct( private FileManager $fileManager, private SecurityConfigUpdater $securityConfigUpdater, private SecurityControllerBuilder $securityControllerBuilder, + private NamespacesHelper $namespacesHelper, ) { } @@ -126,7 +128,7 @@ public function interact(InputInterface $input, ConsoleStyle $io, Command $comma Validator::validateClassName(...) ); - $securityHelper = new InteractiveSecurityHelper(); + $securityHelper = new InteractiveSecurityHelper($this->namespacesHelper); $this->firewallToUpdate = $securityHelper->guessFirewallName($io, $securityData); $this->userClass = $securityHelper->guessUserClass($io, $securityData['security']['providers']); $this->userNameField = $securityHelper->guessUserNameField($io, $this->userClass, $securityData['security']['providers']); diff --git a/src/MakerBundle.php b/src/MakerBundle.php index 42516aec8..c5ee963e4 100644 --- a/src/MakerBundle.php +++ b/src/MakerBundle.php @@ -31,8 +31,42 @@ class MakerBundle extends AbstractBundle public function configure(DefinitionConfigurator $definition): void { $definition->rootNode() + ->beforeNormalization() + ->ifTrue(function ($v) { return isset($v['root_namespace']) && !isset($v['namespaces']['root']); }) + ->then(function ($v) { + $v['namespaces']['root'] = $v['root_namespace']; + + return $v; + }) + ->end() ->children() - ->scalarNode('root_namespace')->defaultValue('App')->end() + ->scalarNode('root_namespace') + ->setDeprecated('symfony/maker-bundle', '2.0', 'The "root_namespace" option is deprecated, use "namespaces.root" instead.') + ->defaultNull() + ->end() + ->arrayNode('namespaces') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('root')->defaultNull()->end() + ->scalarNode('command')->defaultValue('Command\\')->end() + ->scalarNode('controller')->defaultValue('Controller\\')->end() + ->scalarNode('entity')->defaultValue('Entity\\')->end() + ->scalarNode('fixtures')->defaultValue('DataFixtures\\')->end() + ->scalarNode('form')->defaultValue('Form\\')->end() + ->scalarNode('listener')->defaultValue('EventListener\\')->end() + ->scalarNode('message')->defaultValue('Message\\')->end() + ->scalarNode('message_handler')->defaultValue('MessageHandler\\')->end() + ->scalarNode('middleware')->defaultValue('Middleware\\')->end() + ->scalarNode('repository')->defaultValue('Repository\\')->end() + ->scalarNode('scheduler')->defaultValue('Scheduler\\')->end() + ->scalarNode('security')->defaultValue('Security\\')->end() + ->scalarNode('serializer')->defaultValue('Serializer\\')->end() + ->scalarNode('subscriber')->defaultValue('EventSubscriber\\')->end() + ->scalarNode('test')->defaultValue('Tests\\')->end() + ->scalarNode('twig')->defaultValue('Twig\\')->end() + ->scalarNode('validator')->defaultValue('Validator\\')->end() + ->end() + ->end() ->booleanNode('generate_final_classes')->defaultTrue()->end() ->booleanNode('generate_final_entities')->defaultFalse()->end() ->end() @@ -44,19 +78,12 @@ public function loadExtension(array $config, ContainerConfigurator $container, C $container->import('../config/services.xml'); $container->import('../config/makers.xml'); - $rootNamespace = trim($config['root_namespace'], '\\'); - $container->services() - ->get('maker.autoloader_finder') - ->arg(0, $rootNamespace) - ->get('maker.generator') - ->arg(1, $rootNamespace) - ->get('maker.doctrine_helper') - ->arg(0, \sprintf('%s\\Entity', $rootNamespace)) + ->get('maker.namespaces_helper') + ->arg(0, $config['namespaces']) ->get('maker.template_component_generator') ->arg(0, $config['generate_final_classes']) ->arg(1, $config['generate_final_entities']) - ->arg(2, $rootNamespace) ; $builder diff --git a/src/Security/InteractiveSecurityHelper.php b/src/Security/InteractiveSecurityHelper.php index ae35e457a..143baf4ca 100644 --- a/src/Security/InteractiveSecurityHelper.php +++ b/src/Security/InteractiveSecurityHelper.php @@ -14,6 +14,7 @@ use Symfony\Bundle\MakerBundle\Security\Model\Authenticator; use Symfony\Bundle\MakerBundle\Security\Model\AuthenticatorType; use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\NamespacesHelper; use Symfony\Bundle\MakerBundle\Validator; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Security\Core\User\UserInterface; @@ -23,6 +24,10 @@ */ final class InteractiveSecurityHelper { + public function __construct(private NamespacesHelper $namespacesHelper) + { + } + public function guessFirewallName(SymfonyStyle $io, array $securityData, ?string $questionText = null): string { $realFirewalls = array_filter( @@ -53,8 +58,14 @@ public function guessUserClass(SymfonyStyle $io, array $providers, ?string $ques return $entityProvider['entity']['class']; } + $defaultQuestion = \sprintf( + 'Enter the User class that you want to authenticate (e.g. %s\\%s\\User)', + $this->namespacesHelper->getRootNamespace(), + $this->namespacesHelper->getEntityNamespace() + ); + return $io->ask( - $questionText ?? 'Enter the User class that you want to authenticate (e.g. App\\Entity\\User)', + $questionText ?? $defaultQuestion, $this->guessUserClassDefault(), Validator::classIsUserInterface(...) ); @@ -62,12 +73,24 @@ public function guessUserClass(SymfonyStyle $io, array $providers, ?string $ques private function guessUserClassDefault(): string { - if (class_exists('App\\Entity\\User') && isset(class_implements('App\\Entity\\User')[UserInterface::class])) { - return 'App\\Entity\\User'; + $entityUserClass = \sprintf( + '%s\\%s\\User', + $this->namespacesHelper->getRootNamespace(), + $this->namespacesHelper->getEntityNamespace() + ); + + $securityUserClass = \sprintf( + '%s\\%s\\User', + $this->namespacesHelper->getRootNamespace(), + $this->namespacesHelper->getSecurityNamespace() + ); + + if (class_exists($entityUserClass) && isset(class_implements($entityUserClass)[UserInterface::class])) { + return $entityUserClass; } - if (class_exists('App\\Security\\User') && isset(class_implements('App\\Security\\User')[UserInterface::class])) { - return 'App\\Security\\User'; + if (class_exists($securityUserClass) && isset(class_implements($securityUserClass)[UserInterface::class])) { + return $securityUserClass; } return ''; diff --git a/src/Util/ClassSource/Model/ClassData.php b/src/Util/ClassSource/Model/ClassData.php index 4ce0cd605..08938061e 100644 --- a/src/Util/ClassSource/Model/ClassData.php +++ b/src/Util/ClassSource/Model/ClassData.php @@ -27,8 +27,8 @@ private function __construct( public readonly ?string $extends, public readonly bool $isEntity, private UseStatementGenerator $useStatementGenerator, + private string $rootNamespace, private bool $isFinal = true, - private string $rootNamespace = 'App', private ?string $classSuffix = null, ) { if (str_starts_with(haystack: $this->namespace, needle: $this->rootNamespace)) { @@ -36,8 +36,14 @@ private function __construct( } } - public static function create(string $class, ?string $suffix = null, ?string $extendsClass = null, bool $isEntity = false, array $useStatements = []): self - { + public static function create( + string $class, + string $rootNamespace, + ?string $suffix = null, + ?string $extendsClass = null, + bool $isEntity = false, + array $useStatements = [], + ): self { $className = Str::getShortClassName($class); if (null !== $suffix && !str_ends_with($className, $suffix)) { @@ -56,6 +62,7 @@ className: Str::asClassName($className), extends: null === $extendsClass ? null : Str::getShortClassName($extendsClass), isEntity: $isEntity, useStatementGenerator: $useStatements, + rootNamespace: $rootNamespace, classSuffix: $suffix, ); } diff --git a/src/Util/ComposerAutoloaderFinder.php b/src/Util/ComposerAutoloaderFinder.php index ad307df0b..97e7c80e2 100644 --- a/src/Util/ComposerAutoloaderFinder.php +++ b/src/Util/ComposerAutoloaderFinder.php @@ -23,11 +23,11 @@ class ComposerAutoloaderFinder private array $rootNamespace; private ?ClassLoader $classLoader = null; - public function __construct(string $rootNamespace) + public function __construct(NamespacesHelper $namespacesHelper) { $this->rootNamespace = [ - 'psr0' => rtrim($rootNamespace, '\\'), - 'psr4' => rtrim($rootNamespace, '\\').'\\', + 'psr0' => rtrim($namespacesHelper->getRootNamespace(), '\\'), + 'psr4' => rtrim($namespacesHelper->getRootNamespace(), '\\').'\\', ]; } diff --git a/src/Util/NamespacesHelper.php b/src/Util/NamespacesHelper.php new file mode 100644 index 000000000..fc52fbe9e --- /dev/null +++ b/src/Util/NamespacesHelper.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\MakerBundle\Util; + +final class NamespacesHelper +{ + public function __construct(private array $namespaces = []) + { + } + + public function getCommandNamespace(): string + { + return $this->trim($this->namespaces['command'] ?? 'Command\\'); + } + + public function getControllerNamespace(): string + { + return $this->trim($this->namespaces['controller'] ?? 'Controller\\'); + } + + public function getEntityNamespace(): string + { + return $this->trim($this->namespaces['entity'] ?? 'Entity\\'); + } + + public function getFixturesNamespace(): string + { + return $this->trim($this->namespaces['fixtures'] ?? 'DataFixtures\\'); + } + + public function getFormNamespace(): string + { + return $this->trim($this->namespaces['form'] ?? 'Form\\'); + } + + public function getListenerNamespace(): string + { + return $this->trim($this->namespaces['listener'] ?? 'EventListener\\'); + } + + public function getMessageNamespace(): string + { + return $this->trim($this->namespaces['message'] ?? 'Message\\'); + } + + public function getMessageHandlerNamespace(): string + { + return $this->trim($this->namespaces['message_handler'] ?? 'MessageHandler\\'); + } + + public function getMiddlewareNamespace(): string + { + return $this->trim($this->namespaces['middleware'] ?? 'Middleware\\'); + } + + public function getRemoteEventNamespace(): string + { + return $this->trim($this->namespaces['remote_event'] ?? 'RemoteEvent\\'); + } + + public function getRepositoryNamespace(): string + { + return $this->trim($this->namespaces['repository'] ?? 'Repository\\'); + } + + public function getRootNamespace(): string + { + return $this->trim($this->namespaces['root'] ?? 'App\\'); + } + + public function getSchedulerNamespace(): string + { + return $this->trim($this->namespaces['scheduler'] ?? 'Scheduler\\'); + } + + public function getSecurityNamespace(): string + { + return $this->trim($this->namespaces['security'] ?? 'Security\\'); + } + + public function getSerializerNamespace(): string + { + return $this->trim($this->namespaces['serializer'] ?? 'Serializer\\'); + } + + public function getSubscriberNamespace(): string + { + return $this->trim($this->namespaces['subscriber'] ?? 'EventSubscriber\\'); + } + + public function getTestNamespace(): string + { + return $this->trim($this->namespaces['test'] ?? 'Tests\\'); + } + + public function getTwigNamespace(): string + { + return $this->trim($this->namespaces['twig'] ?? 'Twig\\'); + } + + public function getValidatorNamespace(): string + { + return $this->trim($this->namespaces['validator'] ?? 'Validator\\'); + } + + public function getWebhookNamespace(): string + { + return $this->trim($this->namespaces['webhook'] ?? 'Webhook\\'); + } + + private function trim(string $namespace): string + { + return trim($namespace, '\\'); + } +} diff --git a/src/Util/TemplateComponentGenerator.php b/src/Util/TemplateComponentGenerator.php index de8fc163c..7fd041d73 100644 --- a/src/Util/TemplateComponentGenerator.php +++ b/src/Util/TemplateComponentGenerator.php @@ -23,7 +23,7 @@ final class TemplateComponentGenerator public function __construct( private bool $generateFinalClasses, private bool $generateFinalEntities, - private string $rootNamespace, + private NamespacesHelper $namespacesHelper, ) { } @@ -62,7 +62,7 @@ public function getPropertyType(ClassNameDetails $classNameDetails): ?string public function configureClass(ClassData $classMetadata): ClassData { - $classMetadata->setRootNamespace($this->rootNamespace); + $classMetadata->setRootNamespace($this->namespacesHelper->getRootNamespace()); if ($classMetadata->isEntity) { return $classMetadata->setIsFinal($this->generateFinalEntities); diff --git a/tests/Command/MakerCommandTest.php b/tests/Command/MakerCommandTest.php index 06aa9823d..1c208d729 100644 --- a/tests/Command/MakerCommandTest.php +++ b/tests/Command/MakerCommandTest.php @@ -18,6 +18,7 @@ use Symfony\Bundle\MakerBundle\FileManager; use Symfony\Bundle\MakerBundle\Generator; use Symfony\Bundle\MakerBundle\MakerInterface; +use Symfony\Bundle\MakerBundle\Util\NamespacesHelper; use Symfony\Bundle\MakerBundle\Util\TemplateLinter; use Symfony\Component\Console\Tester\CommandTester; @@ -40,7 +41,7 @@ public function testExceptionOnMissingDependencies(): void $fileManager = $this->createMock(FileManager::class); - $command = new MakerCommand($maker, $fileManager, new Generator($fileManager, 'App'), new TemplateLinter()); + $command = new MakerCommand($maker, $fileManager, new Generator($fileManager, new NamespacesHelper()), new TemplateLinter()); // needed because it's normally set by the Application $command->setName('make:foo'); $tester = new CommandTester($command); @@ -53,7 +54,7 @@ public function testExceptionOnUnknownRootNamespace(): void $fileManager = $this->createMock(FileManager::class); - $command = new MakerCommand($maker, $fileManager, new Generator($fileManager, 'Unknown'), new TemplateLinter()); + $command = new MakerCommand($maker, $fileManager, new Generator($fileManager, new NamespacesHelper(['root' => 'Unknown'])), new TemplateLinter()); // needed because it's normally set by the Application $command->setName('make:foo'); $tester = new CommandTester($command); diff --git a/tests/Doctrine/EntityRegeneratorTest.php b/tests/Doctrine/EntityRegeneratorTest.php index 813946b3b..a7d81e6a6 100644 --- a/tests/Doctrine/EntityRegeneratorTest.php +++ b/tests/Doctrine/EntityRegeneratorTest.php @@ -23,6 +23,7 @@ use Symfony\Bundle\MakerBundle\Generator; use Symfony\Bundle\MakerBundle\Util\AutoloaderUtil; use Symfony\Bundle\MakerBundle\Util\MakerFileLinkFormatter; +use Symfony\Bundle\MakerBundle\Util\NamespacesHelper; use Symfony\Bundle\MakerBundle\Util\TemplateComponentGenerator; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -98,10 +99,11 @@ private function doTestRegeneration(string $sourceDir, Kernel $kernel, string $n return $tmpDir.'/src/'.str_replace('\\', '/', $shortClassName).'.php'; }); + $namespacesHelper = new NamespacesHelper(); $fileManager = new FileManager($fs, $autoloaderUtil, new MakerFileLinkFormatter(null), $tmpDir); - $doctrineHelper = new DoctrineHelper('App\\Entity', $container->get('doctrine')); - $templateComponentGenerator = new TemplateComponentGenerator(false, false, 'App'); - $generator = new Generator(fileManager: $fileManager, namespacePrefix: 'App\\', templateComponentGenerator: $templateComponentGenerator); + $doctrineHelper = new DoctrineHelper($namespacesHelper, $container->get('doctrine')); + $templateComponentGenerator = new TemplateComponentGenerator(false, false, $namespacesHelper); + $generator = new Generator(fileManager: $fileManager, namespacesHelper: $namespacesHelper, templateComponentGenerator: $templateComponentGenerator); $entityClassGenerator = new EntityClassGenerator($generator, $doctrineHelper); $regenerator = new EntityRegenerator( $doctrineHelper, diff --git a/tests/GeneratorTest.php b/tests/GeneratorTest.php index 9c1c85f84..7be05b704 100644 --- a/tests/GeneratorTest.php +++ b/tests/GeneratorTest.php @@ -14,20 +14,27 @@ use PHPUnit\Framework\TestCase; use Symfony\Bundle\MakerBundle\FileManager; use Symfony\Bundle\MakerBundle\Generator; +use Symfony\Bundle\MakerBundle\Util\NamespacesHelper; class GeneratorTest extends TestCase { /** * @dataProvider getClassNameDetailsTests */ - public function testCreateClassNameDetails(string $name, string $prefix, string $suffix, string $expectedFullClassName, string $expectedRelativeClassName): void - { + public function testCreateClassNameDetails( + string $name, + string $prefix, + string $suffix, + string $expectedFullClassName, + string $expectedRelativeClassName, + array $namespaces = [], + ): void { $fileManager = $this->createMock(FileManager::class); $fileManager->expects($this->any()) ->method('getNamespacePrefixForClass') ->willReturn('Foo'); - $generator = new Generator($fileManager, 'App\\'); + $generator = new Generator($fileManager, new NamespacesHelper($namespaces)); $classNameDetails = $generator->createClassNameDetails($name, $prefix, $suffix); @@ -100,5 +107,23 @@ public function getClassNameDetailsTests(): \Generator 'Symfony\\Bundle\\MakerBundle\\Tests\\GeneratorTest', 'Symfony\\Bundle\\MakerBundle\\Tests\\GeneratorTest', ]; + + yield 'simple_custom_namespace' => [ + 'foo', + 'Controller\\', + '', + 'Custom\\Controller\\Foo', + 'Foo', + ['root' => 'Custom\\'], + ]; + + yield 'multilevel_custom_namespace' => [ + 'foo', + 'Controller\\', + '', + 'Custom\\Root\\Controller\\Foo', + 'Foo', + ['root' => 'Custom\\Root\\'], + ]; } } diff --git a/tests/Security/InteractiveSecurityHelperTest.php b/tests/Security/InteractiveSecurityHelperTest.php index d28522359..edaa50156 100644 --- a/tests/Security/InteractiveSecurityHelperTest.php +++ b/tests/Security/InteractiveSecurityHelperTest.php @@ -15,6 +15,7 @@ use Symfony\Bundle\MakerBundle\Security\InteractiveSecurityHelper; use Symfony\Bundle\MakerBundle\Security\Model\Authenticator; use Symfony\Bundle\MakerBundle\Security\Model\AuthenticatorType; +use Symfony\Bundle\MakerBundle\Util\NamespacesHelper; use Symfony\Component\Console\Style\SymfonyStyle; class InteractiveSecurityHelperTest extends TestCase @@ -30,7 +31,7 @@ public function testGuessFirewallName(array $securityData, string $expectedFirew ->method('choice') ->willReturn($expectedFirewallName); - $helper = new InteractiveSecurityHelper(); + $helper = new InteractiveSecurityHelper(new NamespacesHelper()); $this->assertEquals( $expectedFirewallName, $helper->guessFirewallName($io, $securityData) @@ -88,7 +89,7 @@ public function testGuessUserClass(array $securityData, string $expectedUserClas ->method('ask') ->willReturn($expectedUserClass); - $helper = new InteractiveSecurityHelper(); + $helper = new InteractiveSecurityHelper(new NamespacesHelper()); $this->assertEquals( $expectedUserClass, $helper->guessUserClass($io, $securityData) @@ -128,7 +129,7 @@ public function testGuessUserNameField(array $providers, string $expectedUsernam ->with(\sprintf('Which field on your %s class will people enter when logging in?', $class), $choices, 'username') ->willReturn($expectedUsernameField); - $interactiveSecurityHelper = new InteractiveSecurityHelper(); + $interactiveSecurityHelper = new InteractiveSecurityHelper(new NamespacesHelper()); $this->assertEquals( $expectedUsernameField, $interactiveSecurityHelper->guessUserNameField($io, $class, $providers) @@ -185,7 +186,7 @@ public function testGuessEmailField(string $expectedEmailField, bool $fieldAutom ->with(\sprintf('Which field on your %s class holds the email address?', $class), $choices, null) ->willReturn($expectedEmailField); - $interactiveSecurityHelper = new InteractiveSecurityHelper(); + $interactiveSecurityHelper = new InteractiveSecurityHelper(new NamespacesHelper()); $this->assertEquals( $expectedEmailField, $interactiveSecurityHelper->guessEmailField($io, $class) @@ -211,7 +212,7 @@ public function guessEmailFieldTest() /** @dataProvider authenticatorClassProvider */ public function testGetAuthenticatorsFromConfig(array $firewalls, array $expectedResults): void { - $helper = new InteractiveSecurityHelper(); + $helper = new InteractiveSecurityHelper(new NamespacesHelper()); $result = $helper->getAuthenticatorsFromConfig($firewalls); self::assertEquals($expectedResults, $result); @@ -287,7 +288,7 @@ public function testGuessPasswordSetter(string $expectedPasswordSetter, bool $au ->with(\sprintf('Which method on your %s class can be used to set the encoded password (e.g. setPassword())?', $class), $choices, null) ->willReturn($expectedPasswordSetter); - $interactiveSecurityHelper = new InteractiveSecurityHelper(); + $interactiveSecurityHelper = new InteractiveSecurityHelper(new NamespacesHelper()); $this->assertEquals( $expectedPasswordSetter, $interactiveSecurityHelper->guessPasswordSetter($io, $class) @@ -322,7 +323,7 @@ public function testGuessEmailGetter(string $expectedEmailGetter, string $emailA ->with(\sprintf('Which method on your %s class can be used to get the email address (e.g. getEmail())?', $class), $choices, null) ->willReturn($expectedEmailGetter); - $interactiveSecurityHelper = new InteractiveSecurityHelper(); + $interactiveSecurityHelper = new InteractiveSecurityHelper(new NamespacesHelper()); $this->assertEquals( $expectedEmailGetter, $interactiveSecurityHelper->guessEmailGetter($io, $class, $emailAttribute) diff --git a/tests/Util/AutoloaderUtilTest.php b/tests/Util/AutoloaderUtilTest.php index 299f04df1..8af7479d0 100644 --- a/tests/Util/AutoloaderUtilTest.php +++ b/tests/Util/AutoloaderUtilTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bundle\MakerBundle\Util\AutoloaderUtil; use Symfony\Bundle\MakerBundle\Util\ComposerAutoloaderFinder; +use Symfony\Bundle\MakerBundle\Util\NamespacesHelper; use Symfony\Component\Filesystem\Filesystem; class AutoloaderUtilTest extends TestCase @@ -96,7 +97,7 @@ private function createComposerAutoloaderFinder(?array $composerJsonParams = nul /** @var \PHPUnit_Framework_MockObject_MockObject|ComposerAutoloaderFinder $finder */ $finder = $this ->getMockBuilder(ComposerAutoloaderFinder::class) - ->setConstructorArgs(['App\\']) + ->setConstructorArgs([new NamespacesHelper()]) ->getMock(); $finder diff --git a/tests/Util/ClassSource/ClassDataTest.php b/tests/Util/ClassSource/ClassDataTest.php index 27d345028..049845dc7 100644 --- a/tests/Util/ClassSource/ClassDataTest.php +++ b/tests/Util/ClassSource/ClassDataTest.php @@ -20,7 +20,7 @@ class ClassDataTest extends TestCase { public function testStaticConstructor(): void { - $meta = ClassData::create(MakerBundle::class); + $meta = ClassData::create(MakerBundle::class, 'App'); // Sanity check in case Maker's NS changes self::assertSame('Symfony\Bundle\MakerBundle\MakerBundle', MakerBundle::class); @@ -32,14 +32,14 @@ public function testStaticConstructor(): void public function testGetClassDeclaration(): void { - $meta = ClassData::create(MakerBundle::class); + $meta = ClassData::create(MakerBundle::class, 'App'); self::assertSame('final class MakerBundle', $meta->getClassDeclaration()); } public function testIsFinal(): void { - $meta = ClassData::create(MakerBundle::class); + $meta = ClassData::create(MakerBundle::class, 'App'); // Default - isFinal - true self::assertSame('final class MakerBundle', $meta->getClassDeclaration()); @@ -51,7 +51,7 @@ public function testIsFinal(): void public function testGetClassDeclarationWithExtends(): void { - $meta = ClassData::create(class: MakerBundle::class, extendsClass: MakerTestKernel::class); + $meta = ClassData::create(class: MakerBundle::class, rootNamespace: 'App', extendsClass: MakerTestKernel::class); self::assertSame('final class MakerBundle extends MakerTestKernel', $meta->getClassDeclaration()); } @@ -59,7 +59,7 @@ public function testGetClassDeclarationWithExtends(): void /** @dataProvider suffixDataProvider */ public function testSuffix(?string $suffix, string $expectedResult): void { - $data = ClassData::create(class: MakerBundle::class, suffix: $suffix); + $data = ClassData::create(class: MakerBundle::class, rootNamespace: 'App', suffix: $suffix); self::assertSame($expectedResult, $data->getClassName()); } @@ -74,7 +74,7 @@ public function suffixDataProvider(): \Generator /** @dataProvider namespaceDataProvider */ public function testNamespace(string $class, ?string $rootNamespace, string $expectedNamespace, string $expectedFullClassName): void { - $class = ClassData::create($class); + $class = ClassData::create($class, 'App'); if (null !== $rootNamespace) { $class->setRootNamespace($rootNamespace); @@ -94,7 +94,7 @@ public function namespaceDataProvider(): \Generator public function testGetClassName(): void { - $class = ClassData::create(class: 'Controller\\Foo', suffix: 'Controller'); + $class = ClassData::create(class: 'Controller\\Foo', rootNamespace: 'App', suffix: 'Controller'); self::assertSame('FooController', $class->getClassName()); self::assertSame('Foo', $class->getClassName(relative: false, withoutSuffix: true)); self::assertSame('FooController', $class->getClassName(relative: true, withoutSuffix: false)); @@ -104,7 +104,7 @@ public function testGetClassName(): void public function testGetClassNameRelativeNamespace(): void { - $class = ClassData::create(class: 'Controller\\Admin\\Foo', suffix: 'Controller'); + $class = ClassData::create(class: 'Controller\\Admin\\Foo', rootNamespace: 'App', suffix: 'Controller'); self::assertSame('FooController', $class->getClassName()); self::assertSame('Foo', $class->getClassName(relative: false, withoutSuffix: true)); self::assertSame('Admin\FooController', $class->getClassName(relative: true, withoutSuffix: false)); @@ -114,7 +114,7 @@ public function testGetClassNameRelativeNamespace(): void public function testGetClassNameWithAbsoluteNamespace(): void { - $class = ClassData::create(class: '\\Foo\\Bar\\Admin\\Baz', suffix: 'Controller'); + $class = ClassData::create(class: '\\Foo\\Bar\\Admin\\Baz', rootNamespace: 'App', suffix: 'Controller'); self::assertSame('BazController', $class->getClassName()); self::assertSame('Foo\Bar\Admin', $class->getNamespace()); self::assertSame('Foo\Bar\Admin\BazController', $class->getFullClassName()); @@ -123,7 +123,7 @@ public function testGetClassNameWithAbsoluteNamespace(): void /** @dataProvider fullClassNameProvider */ public function testGetFullClassName(string $class, ?string $rootNamespace, bool $withoutRootNamespace, bool $withoutSuffix, string $expectedFullClassName): void { - $class = ClassData::create($class, suffix: 'Controller'); + $class = ClassData::create($class, rootNamespace: 'App', suffix: 'Controller'); if (null !== $rootNamespace) { $class->setRootNamespace($rootNamespace); diff --git a/tests/Util/ComposerAutoloaderFinderTest.php b/tests/Util/ComposerAutoloaderFinderTest.php index d4868cf2e..526542450 100644 --- a/tests/Util/ComposerAutoloaderFinderTest.php +++ b/tests/Util/ComposerAutoloaderFinderTest.php @@ -14,6 +14,7 @@ use Composer\Autoload\ClassLoader; use PHPUnit\Framework\TestCase; use Symfony\Bundle\MakerBundle\Util\ComposerAutoloaderFinder; +use Symfony\Bundle\MakerBundle\Util\NamespacesHelper; class ComposerAutoloaderFinderTest extends TestCase { @@ -42,7 +43,7 @@ public function providerNamespaces(): \Generator public function testGetClassLoader($psr0, $psr4) { $this->setupAutoloadFunctions($psr0, $psr4); - $loader = (new ComposerAutoloaderFinder(static::$rootNamespace))->getClassLoader(); + $loader = (new ComposerAutoloaderFinder(new NamespacesHelper(['root' => static::$rootNamespace])))->getClassLoader(); $this->assertInstanceOf(ClassLoader::class, $loader, 'Wrong ClassLoader found'); } @@ -56,7 +57,7 @@ public function testGetClassLoaderWhenItIsEmpty() }; // throws \Exception - (new ComposerAutoloaderFinder(static::$rootNamespace))->getClassLoader(); + (new ComposerAutoloaderFinder(new NamespacesHelper(['root' => static::$rootNamespace])))->getClassLoader(); } /** diff --git a/tests/Util/TemplateComponentGeneratorTest.php b/tests/Util/TemplateComponentGeneratorTest.php index a61bb6bd2..72bc7c278 100644 --- a/tests/Util/TemplateComponentGeneratorTest.php +++ b/tests/Util/TemplateComponentGeneratorTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bundle\MakerBundle\MakerBundle; use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData; +use Symfony\Bundle\MakerBundle\Util\NamespacesHelper; use Symfony\Bundle\MakerBundle\Util\TemplateComponentGenerator; /** @@ -23,7 +24,7 @@ class TemplateComponentGeneratorTest extends TestCase { public function testRouteAttributes(): void { - $generator = new TemplateComponentGenerator(false, false, 'App'); + $generator = new TemplateComponentGenerator(false, false, new NamespacesHelper()); $expected = " #[Route('/', name: 'app_home')]\n"; @@ -35,7 +36,7 @@ public function testRouteAttributes(): void */ public function testRouteMethods(string $expected, array $methods): void { - $generator = new TemplateComponentGenerator(false, false, 'App'); + $generator = new TemplateComponentGenerator(false, false, new NamespacesHelper()); self::assertSame($expected, $generator->generateRouteForControllerMethod( '/', @@ -55,7 +56,7 @@ public function routeMethodDataProvider(): \Generator */ public function testRouteIndentation(string $expected): void { - $generator = new TemplateComponentGenerator(false, false, 'App'); + $generator = new TemplateComponentGenerator(false, false, new NamespacesHelper()); self::assertSame($expected, $generator->generateRouteForControllerMethod( '/', @@ -75,7 +76,7 @@ public function routeIndentationDataProvider(): \Generator */ public function testRouteTrailingNewLine(string $expected): void { - $generator = new TemplateComponentGenerator(false, false, 'App'); + $generator = new TemplateComponentGenerator(false, false, new NamespacesHelper()); self::assertSame($expected, $generator->generateRouteForControllerMethod( '/', @@ -96,9 +97,9 @@ public function routeTrailingNewLineDataProvider(): \Generator */ public function testGetFinalClassDeclaration(bool $finalClass, bool $finalEntity, bool $isEntity, string $expectedResult): void { - $generator = new TemplateComponentGenerator($finalClass, $finalEntity, 'App'); + $generator = new TemplateComponentGenerator($finalClass, $finalEntity, new NamespacesHelper()); - $classData = ClassData::create(MakerBundle::class, isEntity: $isEntity); + $classData = ClassData::create(MakerBundle::class, rootNamespace: 'App\\', isEntity: $isEntity); $generator->configureClass($classData); @@ -119,9 +120,9 @@ public function finalClassDataProvider(): \Generator public function testConfiguresClassDataWithRootNamespace(): void { - $generator = new TemplateComponentGenerator(false, false, 'MakerTest'); + $generator = new TemplateComponentGenerator(false, false, new NamespacesHelper(['root' => 'MakerTest'])); - $classData = ClassData::create(MakerBundle::class); + $classData = ClassData::create(MakerBundle::class, 'App\\'); $generator->configureClass($classData);