From ca3e8c13c5c2bccdadd9f91899f75125124dd860 Mon Sep 17 00:00:00 2001 From: Jesse Rushlow Date: Fri, 30 Aug 2024 00:57:07 -0400 Subject: [PATCH 1/6] [make:controller] generate final controller class --- src/Maker/MakeController.php | 50 ++++++++++++++----- .../skeleton/controller/Controller.tpl.php | 6 +-- src/Util/ClassSource/Model/ClassData.php | 26 +++++++++- tests/Util/ClassSource/ClassDataTest.php | 45 +++++++++++++++++ .../tests/it_generates_a_controller.php | 2 +- .../it_generates_a_controller_with_twig.php | 2 +- .../it_generates_an_invokable_controller.php | 2 +- 7 files changed, 113 insertions(+), 20 deletions(-) diff --git a/src/Maker/MakeController.php b/src/Maker/MakeController.php index 4cf84e1f6..0b628c66f 100644 --- a/src/Maker/MakeController.php +++ b/src/Maker/MakeController.php @@ -17,8 +17,8 @@ use Symfony\Bundle\MakerBundle\Generator; use Symfony\Bundle\MakerBundle\InputConfiguration; use Symfony\Bundle\MakerBundle\Str; +use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData; use Symfony\Bundle\MakerBundle\Util\PhpCompatUtil; -use Symfony\Bundle\MakerBundle\Util\UseStatementGenerator; use Symfony\Bundle\TwigBundle\TwigBundle; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -76,22 +76,48 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen $withTemplate = $this->isTwigInstalled() && !$input->getOption('no-template'); $isInvokable = (bool) $input->getOption('invokable'); - $useStatements = new UseStatementGenerator([ - AbstractController::class, - $withTemplate ? Response::class : JsonResponse::class, - Route::class, - ]); + $controllerClass = $input->getArgument('controller-class'); - $templateName = Str::asFilePath($controllerClassNameDetails->getRelativeNameWithoutSuffix()) + $controllerClassData = ClassData::create( + class: '\\' === $controllerClass[0] ? substr($controllerClass, 1) : \sprintf('Controller\%s', $input->getArgument('controller-class')), + suffix: 'Controller', + extendsClass: AbstractController::class, + useStatements: [ + $withTemplate ? Response::class : JsonResponse::class, + Route::class, + ] + ); + + // dd([ + // $controllerClassNameDetails, + // $controllerClassNameDetails->getRelativeName(), + // $controllerClassNameDetails->getShortName(), + // $controllerClassNameDetails->getFullName(), + // $controllerClassNameDetails->getRelativeNameWithoutSuffix(), + // ], + // [ + // $controllerClassData, + // $controllerClassData->getClassName(relative: true), + // $controllerClassData->getClassName(), + // $controllerClassData->getFullClassName(), + // $controllerClassData->getClassName(relative: true, withoutSuffix: true), + // ] + // ); + + // $templateName = Str::asFilePath($controllerClassNameDetails->getRelativeNameWithoutSuffix()) + $templateName = Str::asFilePath($controllerClassData->getClassName(relative: true, withoutSuffix: true)) .($isInvokable ? '.html.twig' : '/index.html.twig'); $controllerPath = $generator->generateController( - $controllerClassNameDetails->getFullName(), + $controllerClassData->getFullClassName(), 'controller/Controller.tpl.php', [ - 'use_statements' => $useStatements, - 'route_path' => Str::asRoutePath($controllerClassNameDetails->getRelativeNameWithoutSuffix()), - 'route_name' => Str::asRouteName($controllerClassNameDetails->getRelativeNameWithoutSuffix()), + 'class_data' => $controllerClassData, + // 'use_statements' => $useStatements, + // 'route_path' => Str::asRoutePath($controllerClassNameDetails->getRelativeNameWithoutSuffix()), + 'route_path' => Str::asRoutePath($controllerClassData->getClassName(relative: true, withoutSuffix: true)), + 'route_name' => Str::AsRouteName($controllerClassData->getClassName(relative: true, withoutSuffix: true)), + // 'route_name' => Str::asRouteName($controllerClassNameDetails->getRelativeNameWithoutSuffix()), 'method_name' => $isInvokable ? '__invoke' : 'index', 'with_template' => $withTemplate, 'template_name' => $templateName, @@ -105,7 +131,7 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen [ 'controller_path' => $controllerPath, 'root_directory' => $generator->getRootDirectory(), - 'class_name' => $controllerClassNameDetails->getShortName(), + 'class_name' => $controllerClassData->getClassName(), ] ); } diff --git a/src/Resources/skeleton/controller/Controller.tpl.php b/src/Resources/skeleton/controller/Controller.tpl.php index 8f509e699..7d8d6fe4e 100644 --- a/src/Resources/skeleton/controller/Controller.tpl.php +++ b/src/Resources/skeleton/controller/Controller.tpl.php @@ -1,10 +1,10 @@ -namespace ; +namespace getNamespace(); ?>; - +getUseStatements(); ?> -class extends AbstractController +getClassDeclaration(); ?> { generateRouteForControllerMethod($route_path, $route_name); ?> public function (): ResponseJsonResponse diff --git a/src/Util/ClassSource/Model/ClassData.php b/src/Util/ClassSource/Model/ClassData.php index 988404f2a..975c1c31b 100644 --- a/src/Util/ClassSource/Model/ClassData.php +++ b/src/Util/ClassSource/Model/ClassData.php @@ -29,7 +29,11 @@ private function __construct( private UseStatementGenerator $useStatementGenerator, private bool $isFinal = true, private string $rootNamespace = 'App', + private ?string $classSuffix = null, ) { + if (str_starts_with(haystack: $this->namespace, needle: $this->rootNamespace)) { + $this->namespace = substr_replace(string: $this->namespace, replace: '', offset: 0, length: \strlen($this->rootNamespace) + 1); + } } public static function create(string $class, ?string $suffix = null, ?string $extendsClass = null, bool $isEntity = false, array $useStatements = []): self @@ -52,12 +56,30 @@ className: Str::asClassName($className), extends: null === $extendsClass ? null : Str::getShortClassName($extendsClass), isEntity: $isEntity, useStatementGenerator: $useStatements, + classSuffix: $suffix, ); } - public function getClassName(): string + public function getClassName(bool $relative = false, bool $withoutSuffix = false): string { - return $this->className; + if (!$withoutSuffix && !$relative) { + return $this->className; + } + + if ($relative) { + $class = \sprintf('%s\%s', $this->namespace, $this->className); + + $firstNsSeparatorPosition = stripos($class, '\\'); + $class = substr_replace(string: $class, replace: '', offset: 0, length: $firstNsSeparatorPosition + 1); + + if ($withoutSuffix) { + $class = Str::removeSuffix($class, $this->classSuffix); + } + + return $class; + } + + return Str::removeSuffix($this->className, $this->classSuffix); } public function getNamespace(): string diff --git a/tests/Util/ClassSource/ClassDataTest.php b/tests/Util/ClassSource/ClassDataTest.php index cbad75dec..f1c2594e8 100644 --- a/tests/Util/ClassSource/ClassDataTest.php +++ b/tests/Util/ClassSource/ClassDataTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bundle\MakerBundle\MakerBundle; use Symfony\Bundle\MakerBundle\Test\MakerTestKernel; +use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData; class ClassDataTest extends TestCase @@ -91,4 +92,48 @@ public function namespaceDataProvider(): \Generator yield ['MyController', 'Maker', 'Maker', 'Maker\MyController']; yield ['Controller\MyController', 'Maker', 'Maker\Controller', 'Maker\Controller\MyController']; } + + public function testGetClassName(): void + { + $class = ClassData::create(class: 'Controller\\Foo', 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)); + self::assertSame('Foo', $class->getClassName(relative: true, withoutSuffix: true)); + self::assertSame('App\Controller\FooController', $class->getFullClassName()); + } + + public function testGetClassNameRelativeNamespace(): void + { + $class = ClassData::create(class: 'Controller\\Admin\\Foo', 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)); + self::assertSame('Admin\Foo', $class->getClassName(relative: true, withoutSuffix: true)); + self::assertSame('App\Controller\Admin\FooController', $class->getFullClassName()); + } + + public function testGetClassNameWithAbsoluteNamespace(): void + { + $class = ClassData::create(class: '\\Foo\\Bar\\Admin\\Baz', suffix: 'Controller'); + self::assertSame('BazController', $class->getClassName()); + self::assertSame('Baz', $class->getClassName(relative: false, withoutSuffix: true)); + // self::assertSame('Admin\FooController', $class->getClassName(relative: true, withoutSuffix: false)); + // self::assertSame('Admin\Baz', $class->getClassName(relative: true, withoutSuffix: true)); + self::assertSame('Foo\Bar\Admin\BazController', $class->getFullClassName()); + } + + // public function testClassNameDetails(): void + // { + // $class = new ClassNameDetails( + // fullClassName: 'Foo', + // namespacePrefix: 'Controller\\', + // suffix: 'Controller', + // ); + // + // self::assertSame('FooController', $class->getFullName()); + // self::assertSame('MyController', $class->getShortName()); + // self::assertSame('My', $class->getRelativeNameWithoutSuffix()); + // self::assertSame('MyController', $class->getRelativeName()); + // } } diff --git a/tests/fixtures/make-controller/tests/it_generates_a_controller.php b/tests/fixtures/make-controller/tests/it_generates_a_controller.php index 8884cd1fa..f0a6a5651 100644 --- a/tests/fixtures/make-controller/tests/it_generates_a_controller.php +++ b/tests/fixtures/make-controller/tests/it_generates_a_controller.php @@ -4,7 +4,7 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; -class GeneratedControllerTest extends WebTestCase +final class GeneratedControllerTest extends WebTestCase { public function testController() { diff --git a/tests/fixtures/make-controller/tests/it_generates_a_controller_with_twig.php b/tests/fixtures/make-controller/tests/it_generates_a_controller_with_twig.php index c21887ea1..e3980fe57 100644 --- a/tests/fixtures/make-controller/tests/it_generates_a_controller_with_twig.php +++ b/tests/fixtures/make-controller/tests/it_generates_a_controller_with_twig.php @@ -4,7 +4,7 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; -class GeneratedControllerTest extends WebTestCase +final class GeneratedControllerTest extends WebTestCase { public function testController() { diff --git a/tests/fixtures/make-controller/tests/it_generates_an_invokable_controller.php b/tests/fixtures/make-controller/tests/it_generates_an_invokable_controller.php index ed26ac147..6e6a80a22 100644 --- a/tests/fixtures/make-controller/tests/it_generates_an_invokable_controller.php +++ b/tests/fixtures/make-controller/tests/it_generates_an_invokable_controller.php @@ -4,7 +4,7 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; -class GeneratedControllerTest extends WebTestCase +final class GeneratedControllerTest extends WebTestCase { public function testControllerValidity() { From deb71d3c2e46f54550123e0b98bb8f3ccab716dc Mon Sep 17 00:00:00 2001 From: Jesse Rushlow Date: Wed, 25 Sep 2024 13:53:07 -0400 Subject: [PATCH 2/6] add generated file assertions --- tests/Maker/MakeControllerTest.php | 13 ++++++++++++- .../expected/FinalController.php | 19 +++++++++++++++++++ .../expected/FinalControllerWithTemplate.php | 18 ++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/make-controller/expected/FinalController.php create mode 100644 tests/fixtures/make-controller/expected/FinalControllerWithTemplate.php diff --git a/tests/Maker/MakeControllerTest.php b/tests/Maker/MakeControllerTest.php index bce5e787f..8ef1e6713 100644 --- a/tests/Maker/MakeControllerTest.php +++ b/tests/Maker/MakeControllerTest.php @@ -37,8 +37,13 @@ public function getTestDetails(): \Generator ]); $this->assertContainsCount('created: ', $output, 1); - $this->runControllerTest($runner, 'it_generates_a_controller.php'); + + // Ensure the generated controller matches what we expect + self::assertSame( + expected: file_get_contents(\dirname(__DIR__).'/fixtures/make-controller/expected/FinalController.php'), + actual: file_get_contents($runner->getPath('src/Controller/FooBarController.php')) + ); }), ]; @@ -66,6 +71,12 @@ public function getTestDetails(): \Generator self::assertFileExists($controllerPath); $this->runControllerTest($runner, 'it_generates_a_controller_with_twig.php'); + + // Ensure the generated controller matches what we expect + self::assertSame( + expected: file_get_contents(\dirname(__DIR__).'/fixtures/make-controller/expected/FinalControllerWithTemplate.php'), + actual: file_get_contents($runner->getPath('src/Controller/FooTwigController.php')) + ); }), ]; diff --git a/tests/fixtures/make-controller/expected/FinalController.php b/tests/fixtures/make-controller/expected/FinalController.php new file mode 100644 index 000000000..30dcd8f1b --- /dev/null +++ b/tests/fixtures/make-controller/expected/FinalController.php @@ -0,0 +1,19 @@ +json([ + 'message' => 'Welcome to your new controller!', + 'path' => 'src/Controller/FooBarController.php', + ]); + } +} diff --git a/tests/fixtures/make-controller/expected/FinalControllerWithTemplate.php b/tests/fixtures/make-controller/expected/FinalControllerWithTemplate.php new file mode 100644 index 000000000..0a1c4e566 --- /dev/null +++ b/tests/fixtures/make-controller/expected/FinalControllerWithTemplate.php @@ -0,0 +1,18 @@ +render('foo_twig/index.html.twig', [ + 'controller_name' => 'FooTwigController', + ]); + } +} From 4ac2b4b22f38477e7dd4e88ed85585fd120d1850 Mon Sep 17 00:00:00 2001 From: Jesse Rushlow Date: Wed, 25 Sep 2024 13:54:16 -0400 Subject: [PATCH 3/6] add ability to generate a twig template based off of an absolute class name --- src/Maker/MakeController.php | 25 +++++------------------- src/Util/ClassSource/Model/ClassData.php | 18 +++++++++++++++-- tests/Util/ClassSource/ClassDataTest.php | 25 ++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/Maker/MakeController.php b/src/Maker/MakeController.php index 0b628c66f..b89ddf09e 100644 --- a/src/Maker/MakeController.php +++ b/src/Maker/MakeController.php @@ -77,9 +77,10 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen $isInvokable = (bool) $input->getOption('invokable'); $controllerClass = $input->getArgument('controller-class'); + $isAbsolute = '\\' === $controllerClass[0]; $controllerClassData = ClassData::create( - class: '\\' === $controllerClass[0] ? substr($controllerClass, 1) : \sprintf('Controller\%s', $input->getArgument('controller-class')), + class: $isAbsolute ? substr($controllerClass, 1) : \sprintf('Controller\%s', $input->getArgument('controller-class')), suffix: 'Controller', extendsClass: AbstractController::class, useStatements: [ @@ -88,25 +89,9 @@ class: '\\' === $controllerClass[0] ? substr($controllerClass, 1) : \sprintf('Co ] ); - // dd([ - // $controllerClassNameDetails, - // $controllerClassNameDetails->getRelativeName(), - // $controllerClassNameDetails->getShortName(), - // $controllerClassNameDetails->getFullName(), - // $controllerClassNameDetails->getRelativeNameWithoutSuffix(), - // ], - // [ - // $controllerClassData, - // $controllerClassData->getClassName(relative: true), - // $controllerClassData->getClassName(), - // $controllerClassData->getFullClassName(), - // $controllerClassData->getClassName(relative: true, withoutSuffix: true), - // ] - // ); - - // $templateName = Str::asFilePath($controllerClassNameDetails->getRelativeNameWithoutSuffix()) - $templateName = Str::asFilePath($controllerClassData->getClassName(relative: true, withoutSuffix: true)) - .($isInvokable ? '.html.twig' : '/index.html.twig'); + $templateName = Str::asFilePath($isAbsolute ? $controllerClassData->getFullClassName(withoutRootNamespace: true, withoutSuffix: true) : $controllerClassData->getClassName(relative: true, withoutSuffix: true)) + .($isInvokable ? '.html.twig' : '/index.html.twig') + ; $controllerPath = $generator->generateController( $controllerClassData->getFullClassName(), diff --git a/src/Util/ClassSource/Model/ClassData.php b/src/Util/ClassSource/Model/ClassData.php index 975c1c31b..53579a95c 100644 --- a/src/Util/ClassSource/Model/ClassData.php +++ b/src/Util/ClassSource/Model/ClassData.php @@ -91,9 +91,23 @@ public function getNamespace(): string return \sprintf('%s\%s', $this->rootNamespace, $this->namespace); } - public function getFullClassName(): string + /** + * Get the full class name. + * + * @param bool $withoutRootNamespace Get the full class name without global root namespace. e.g. "App" + * @param bool $withoutSuffix Get the full class name without the class suffix. e.g. "MyController" instead of "MyControllerController" + */ + public function getFullClassName($withoutRootNamespace = false, $withoutSuffix = false): string { - return \sprintf('%s\%s', $this->getNamespace(), $this->className); + $className = \sprintf('%s\%s', $this->getNamespace(), $withoutSuffix ? Str::removeSuffix($this->className, $this->classSuffix) : $this->className); + + if ($withoutRootNamespace) { + if (str_starts_with(haystack: $className, needle: $this->rootNamespace)) { + $className = substr_replace(string: $className, replace: '', offset: 0, length: \strlen($this->rootNamespace) + 1); + } + } + + return $className; } public function setRootNamespace(string $rootNamespace): self diff --git a/tests/Util/ClassSource/ClassDataTest.php b/tests/Util/ClassSource/ClassDataTest.php index f1c2594e8..c0990041d 100644 --- a/tests/Util/ClassSource/ClassDataTest.php +++ b/tests/Util/ClassSource/ClassDataTest.php @@ -115,6 +115,7 @@ public function testGetClassNameRelativeNamespace(): void public function testGetClassNameWithAbsoluteNamespace(): void { + $this->markTestSkipped(); $class = ClassData::create(class: '\\Foo\\Bar\\Admin\\Baz', suffix: 'Controller'); self::assertSame('BazController', $class->getClassName()); self::assertSame('Baz', $class->getClassName(relative: false, withoutSuffix: true)); @@ -123,6 +124,30 @@ public function testGetClassNameWithAbsoluteNamespace(): void self::assertSame('Foo\Bar\Admin\BazController', $class->getFullClassName()); } + /** @dataProvider fullClassNameProvider */ + public function testGetFullClassName(string $class, ?string $rootNamespace, bool $withoutRootNamespace, bool $withoutSuffix, string $expectedFullClassName): void + { + $class = ClassData::create($class, suffix: 'Controller'); + + if (null !== $rootNamespace) { + $class->setRootNamespace($rootNamespace); + } + + self::assertSame($expectedFullClassName, $class->getFullClassName(withoutRootNamespace: $withoutRootNamespace, withoutSuffix: $withoutSuffix)); + } + + public function fullClassNameProvider(): \Generator + { + yield ['Controller\MyController', null, false, false, 'App\Controller\MyController']; + yield ['Controller\MyController', null, true, false, 'Controller\MyController']; + yield ['Controller\MyController', null, false, true, 'App\Controller\My']; + yield ['Controller\MyController', null, true, true, 'Controller\My']; + yield ['Controller\MyController', 'Custom', false, false, 'Custom\Controller\MyController']; + yield ['Controller\MyController', 'Custom', true, false, 'Controller\MyController']; + yield ['Controller\MyController', 'Custom', false, true, 'Custom\Controller\My']; + yield ['Controller\MyController', 'Custom', true, true, 'Controller\My']; + } + // public function testClassNameDetails(): void // { // $class = new ClassNameDetails( From d372c76b7f22bc3d011371e7d2ed754496829fe1 Mon Sep 17 00:00:00 2001 From: Jesse Rushlow Date: Wed, 25 Sep 2024 14:19:33 -0400 Subject: [PATCH 4/6] generate the controller from the class data method --- src/Generator.php | 15 +++++--- src/Maker/MakeController.php | 38 +++++++++++-------- .../skeleton/controller/Controller.tpl.php | 2 +- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/Generator.php b/src/Generator.php index 5f744594c..5fc2ebce9 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -83,12 +83,15 @@ public function generateClass(string $className, string $templateName, array $va * * @param string $templateName Template name in Resources/skeleton to use * @param array $variables Array of variables to pass to the template + * @param bool $isController Set to true if generating a Controller that needs + * access to the TemplateComponentGenerator ("generator") in + * the twig template. e.g. to create route attributes on the index method * * @return string The path where the file will be created * * @throws \Exception */ - final public function generateClassFromClassData(ClassData $classData, string $templateName, array $variables = []): string + final public function generateClassFromClassData(ClassData $classData, string $templateName, array $variables = [], bool $isController = false): string { $classData = $this->templateComponentGenerator->configureClass($classData); $targetPath = $this->fileManager->getRelativePathForFutureClass($classData->getFullClassName()); @@ -97,11 +100,13 @@ final public function generateClassFromClassData(ClassData $classData, string $t throw new \LogicException(\sprintf('Could not determine where to locate the new class "%s", maybe try with a full namespace like "\\My\\Full\\Namespace\\%s"', $classData->getFullClassName(), $classData->getClassName())); } - $variables = array_merge($variables, [ - 'class_data' => $classData, - ]); + $globalTemplateVars = ['class_data' => $classData]; - $this->addOperation($targetPath, $templateName, $variables); + if ($isController) { + $globalTemplateVars['generator'] = $this->templateComponentGenerator; + } + + $this->addOperation($targetPath, $templateName, array_merge($variables, $globalTemplateVars)); return $targetPath; } diff --git a/src/Maker/MakeController.php b/src/Maker/MakeController.php index b89ddf09e..128e8d58d 100644 --- a/src/Maker/MakeController.php +++ b/src/Maker/MakeController.php @@ -93,21 +93,29 @@ class: $isAbsolute ? substr($controllerClass, 1) : \sprintf('Controller\%s', $in .($isInvokable ? '.html.twig' : '/index.html.twig') ; - $controllerPath = $generator->generateController( - $controllerClassData->getFullClassName(), - 'controller/Controller.tpl.php', - [ - 'class_data' => $controllerClassData, - // 'use_statements' => $useStatements, - // 'route_path' => Str::asRoutePath($controllerClassNameDetails->getRelativeNameWithoutSuffix()), - 'route_path' => Str::asRoutePath($controllerClassData->getClassName(relative: true, withoutSuffix: true)), - 'route_name' => Str::AsRouteName($controllerClassData->getClassName(relative: true, withoutSuffix: true)), - // 'route_name' => Str::asRouteName($controllerClassNameDetails->getRelativeNameWithoutSuffix()), - 'method_name' => $isInvokable ? '__invoke' : 'index', - 'with_template' => $withTemplate, - 'template_name' => $templateName, - ] - ); + $controllerPath = $generator->generateClassFromClassData($controllerClassData, 'controller/Controller.tpl.php', [ + 'route_path' => Str::asRoutePath($controllerClassData->getClassName(relative: true, withoutSuffix: true)), + 'route_name' => Str::AsRouteName($controllerClassData->getClassName(relative: true, withoutSuffix: true)), + 'method_name' => $isInvokable ? '__invoke' : 'index', + 'with_template' => $withTemplate, + 'template_name' => $templateName, + ], true); + + // $controllerPath = $generator->generateController( + // $controllerClassData->getFullClassName(), + // 'controller/Controller.tpl.php', + // [ + // 'class_data' => $controllerClassData, + // // 'use_statements' => $useStatements, + // // 'route_path' => Str::asRoutePath($controllerClassNameDetails->getRelativeNameWithoutSuffix()), + // 'route_path' => Str::asRoutePath($controllerClassData->getClassName(relative: true, withoutSuffix: true)), + // 'route_name' => Str::AsRouteName($controllerClassData->getClassName(relative: true, withoutSuffix: true)), + // // 'route_name' => Str::asRouteName($controllerClassNameDetails->getRelativeNameWithoutSuffix()), + // 'method_name' => $isInvokable ? '__invoke' : 'index', + // 'with_template' => $withTemplate, + // 'template_name' => $templateName, + // ] + // ); if ($withTemplate) { $generator->generateTemplate( diff --git a/src/Resources/skeleton/controller/Controller.tpl.php b/src/Resources/skeleton/controller/Controller.tpl.php index 7d8d6fe4e..9d9948457 100644 --- a/src/Resources/skeleton/controller/Controller.tpl.php +++ b/src/Resources/skeleton/controller/Controller.tpl.php @@ -12,7 +12,7 @@ public function (): Response return $this->render('', [ - 'controller_name' => '', + 'controller_name' => 'getClassName() ?>', ]); return $this->json([ From 0428618b33878299d82540c1cd4476cdfd467aa2 Mon Sep 17 00:00:00 2001 From: Jesse Rushlow Date: Wed, 25 Sep 2024 14:53:29 -0400 Subject: [PATCH 5/6] cleanup makeController and generator --- src/Generator.php | 2 +- src/Maker/MakeController.php | 48 +++++++++++++++--------------------- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/src/Generator.php b/src/Generator.php index 5fc2ebce9..aa9408311 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -85,7 +85,7 @@ public function generateClass(string $className, string $templateName, array $va * @param array $variables Array of variables to pass to the template * @param bool $isController Set to true if generating a Controller that needs * access to the TemplateComponentGenerator ("generator") in - * the twig template. e.g. to create route attributes on the index method + * the twig template. e.g. to create route attributes for a route method * * @return string The path where the file will be created * diff --git a/src/Maker/MakeController.php b/src/Maker/MakeController.php index 128e8d58d..429502ae1 100644 --- a/src/Maker/MakeController.php +++ b/src/Maker/MakeController.php @@ -67,20 +67,20 @@ public function configureCommand(Command $command, InputConfiguration $inputConf public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator): void { - $controllerClassNameDetails = $generator->createClassNameDetails( - $input->getArgument('controller-class'), - 'Controller\\', - 'Controller' - ); - $withTemplate = $this->isTwigInstalled() && !$input->getOption('no-template'); $isInvokable = (bool) $input->getOption('invokable'); $controllerClass = $input->getArgument('controller-class'); - $isAbsolute = '\\' === $controllerClass[0]; + $controllerClassName = \sprintf('Controller\%s', $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 + if ($isAbsolute = '\\' === $controllerClass[0]) { + $controllerClassName = substr($controllerClass, 1); + } $controllerClassData = ClassData::create( - class: $isAbsolute ? substr($controllerClass, 1) : \sprintf('Controller\%s', $input->getArgument('controller-class')), + class: $controllerClassName, suffix: 'Controller', extendsClass: AbstractController::class, useStatements: [ @@ -89,37 +89,29 @@ class: $isAbsolute ? substr($controllerClass, 1) : \sprintf('Controller\%s', $in ] ); - $templateName = Str::asFilePath($isAbsolute ? $controllerClassData->getFullClassName(withoutRootNamespace: true, withoutSuffix: true) : $controllerClassData->getClassName(relative: true, withoutSuffix: true)) - .($isInvokable ? '.html.twig' : '/index.html.twig') + // Again if the class name is absolute, lets not make assumptions about where the twig template + // should live. E.g. templates/custom/location/for/my_controller.html.twig instead of + // templates/my/controller.html.twig. We do however remove the root_namespace prefix in either case + // so we don't end up with templates/app/my/controller.html.twig + $templateName = $isAbsolute ? + $controllerClassData->getFullClassName(withoutRootNamespace: true, withoutSuffix: true) : + $controllerClassData->getClassName(relative: true, withoutSuffix: true) ; + // Convert the twig template name into a file path where it will be generated. + $templatePath = \sprintf('%s%s', Str::asFilePath($templateName), $isInvokable ? '.html.twig' : '/index.html.twig'); + $controllerPath = $generator->generateClassFromClassData($controllerClassData, 'controller/Controller.tpl.php', [ 'route_path' => Str::asRoutePath($controllerClassData->getClassName(relative: true, withoutSuffix: true)), 'route_name' => Str::AsRouteName($controllerClassData->getClassName(relative: true, withoutSuffix: true)), 'method_name' => $isInvokable ? '__invoke' : 'index', 'with_template' => $withTemplate, - 'template_name' => $templateName, + 'template_name' => $templatePath, ], true); - // $controllerPath = $generator->generateController( - // $controllerClassData->getFullClassName(), - // 'controller/Controller.tpl.php', - // [ - // 'class_data' => $controllerClassData, - // // 'use_statements' => $useStatements, - // // 'route_path' => Str::asRoutePath($controllerClassNameDetails->getRelativeNameWithoutSuffix()), - // 'route_path' => Str::asRoutePath($controllerClassData->getClassName(relative: true, withoutSuffix: true)), - // 'route_name' => Str::AsRouteName($controllerClassData->getClassName(relative: true, withoutSuffix: true)), - // // 'route_name' => Str::asRouteName($controllerClassNameDetails->getRelativeNameWithoutSuffix()), - // 'method_name' => $isInvokable ? '__invoke' : 'index', - // 'with_template' => $withTemplate, - // 'template_name' => $templateName, - // ] - // ); - if ($withTemplate) { $generator->generateTemplate( - $templateName, + $templatePath, 'controller/twig_template.tpl.php', [ 'controller_path' => $controllerPath, From fc279fe501ee83969a23f742c1192cca8bbeef4c Mon Sep 17 00:00:00 2001 From: Jesse Rushlow Date: Wed, 25 Sep 2024 15:23:32 -0400 Subject: [PATCH 6/6] allow creating class data objects with absolute namespaces --- src/Util/ClassSource/Model/ClassData.php | 5 +++++ tests/Util/ClassSource/ClassDataTest.php | 20 +------------------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/Util/ClassSource/Model/ClassData.php b/src/Util/ClassSource/Model/ClassData.php index 53579a95c..4ce0cd605 100644 --- a/src/Util/ClassSource/Model/ClassData.php +++ b/src/Util/ClassSource/Model/ClassData.php @@ -88,6 +88,11 @@ public function getNamespace(): string return $this->rootNamespace; } + // Namespace is already absolute, don't add the rootNamespace. + if (str_starts_with($this->namespace, '\\')) { + return substr_replace($this->namespace, '', 0, 1); + } + return \sprintf('%s\%s', $this->rootNamespace, $this->namespace); } diff --git a/tests/Util/ClassSource/ClassDataTest.php b/tests/Util/ClassSource/ClassDataTest.php index c0990041d..27d345028 100644 --- a/tests/Util/ClassSource/ClassDataTest.php +++ b/tests/Util/ClassSource/ClassDataTest.php @@ -14,7 +14,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Bundle\MakerBundle\MakerBundle; use Symfony\Bundle\MakerBundle\Test\MakerTestKernel; -use Symfony\Bundle\MakerBundle\Util\ClassNameDetails; use Symfony\Bundle\MakerBundle\Util\ClassSource\Model\ClassData; class ClassDataTest extends TestCase @@ -115,12 +114,9 @@ public function testGetClassNameRelativeNamespace(): void public function testGetClassNameWithAbsoluteNamespace(): void { - $this->markTestSkipped(); $class = ClassData::create(class: '\\Foo\\Bar\\Admin\\Baz', suffix: 'Controller'); self::assertSame('BazController', $class->getClassName()); - self::assertSame('Baz', $class->getClassName(relative: false, withoutSuffix: true)); - // self::assertSame('Admin\FooController', $class->getClassName(relative: true, withoutSuffix: false)); - // self::assertSame('Admin\Baz', $class->getClassName(relative: true, withoutSuffix: true)); + self::assertSame('Foo\Bar\Admin', $class->getNamespace()); self::assertSame('Foo\Bar\Admin\BazController', $class->getFullClassName()); } @@ -147,18 +143,4 @@ public function fullClassNameProvider(): \Generator yield ['Controller\MyController', 'Custom', false, true, 'Custom\Controller\My']; yield ['Controller\MyController', 'Custom', true, true, 'Controller\My']; } - - // public function testClassNameDetails(): void - // { - // $class = new ClassNameDetails( - // fullClassName: 'Foo', - // namespacePrefix: 'Controller\\', - // suffix: 'Controller', - // ); - // - // self::assertSame('FooController', $class->getFullName()); - // self::assertSame('MyController', $class->getShortName()); - // self::assertSame('My', $class->getRelativeNameWithoutSuffix()); - // self::assertSame('MyController', $class->getRelativeName()); - // } }