Skip to content

Commit c52c44f

Browse files
committed
minor #918 [internal] add route generator for controllers (jrushlow)
This PR was squashed before being merged into the 1.0-dev branch. Discussion ---------- [internal] add route generator for controllers Concept to cleanup templates that depend on route annotations / attributes for better DX when contributing to MakerBundle. The `ResetPasswordController` is an exception due to the existing docBlocks that are generated. Will revisit that controller in the future. Commits ------- 04f9c0b [internal] add route generator for controllers
2 parents de749e3 + 04f9c0b commit c52c44f

File tree

7 files changed

+198
-57
lines changed

7 files changed

+198
-57
lines changed

src/Generator.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
1717
use Symfony\Bundle\MakerBundle\Util\ClassNameDetails;
1818
use Symfony\Bundle\MakerBundle\Util\PhpCompatUtil;
19+
use Symfony\Bundle\MakerBundle\Util\TemplateComponentGenerator;
1920

2021
/**
2122
* @author Javier Eguiluz <[email protected]>
@@ -28,8 +29,9 @@ class Generator
2829
private $pendingOperations = [];
2930
private $namespacePrefix;
3031
private $phpCompatUtil;
32+
private $templateComponentGenerator;
3133

32-
public function __construct(FileManager $fileManager, string $namespacePrefix, PhpCompatUtil $phpCompatUtil = null)
34+
public function __construct(FileManager $fileManager, string $namespacePrefix, PhpCompatUtil $phpCompatUtil = null, TemplateComponentGenerator $templateComponentGenerator = null)
3335
{
3436
$this->fileManager = $fileManager;
3537
$this->twigHelper = new GeneratorTwigHelper($fileManager);
@@ -42,6 +44,7 @@ public function __construct(FileManager $fileManager, string $namespacePrefix, P
4244
}
4345

4446
$this->phpCompatUtil = $phpCompatUtil;
47+
$this->templateComponentGenerator = $templateComponentGenerator;
4548
}
4649

4750
/**
@@ -224,6 +227,7 @@ public function generateController(string $controllerClassName, string $controll
224227
$controllerTemplatePath,
225228
$parameters +
226229
[
230+
'generator' => $this->templateComponentGenerator,
227231
'parent_class_name' => static::getControllerBaseClass()->getShortName(),
228232
]
229233
);

src/Resources/config/services.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
<argument type="service" id="maker.file_manager" />
5151
<argument /> <!-- root namespace -->
5252
<argument type="service" id="maker.php_compat_util" />
53+
<argument type="service" id="maker.template_component_generator" />
5354
</service>
5455

5556
<service id="maker.entity_class_generator" class="Symfony\Bundle\MakerBundle\Doctrine\EntityClassGenerator">
@@ -68,5 +69,9 @@
6869
<service id="maker.php_compat_util" class="Symfony\Bundle\MakerBundle\Util\PhpCompatUtil">
6970
<argument type="service" id="maker.file_manager" />
7071
</service>
72+
73+
<service id="maker.template_component_generator" class="Symfony\Bundle\MakerBundle\Util\TemplateComponentGenerator">
74+
<argument type="service" id="maker.php_compat_util" />
75+
</service>
7176
</services>
7277
</container>

src/Resources/skeleton/controller/Controller.tpl.php

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,7 @@
88

99
class <?= $class_name; ?> extends <?= $parent_class_name; ?><?= "\n" ?>
1010
{
11-
<?php if ($use_attributes) { ?>
12-
#[Route('<?= $route_path ?>', name: '<?= $route_name ?>')]
13-
<?php } else { ?>
14-
/**
15-
* @Route("<?= $route_path ?>", name="<?= $route_name ?>")
16-
*/
17-
<?php } ?>
11+
<?= $generator->generateRouteForControllerMethod($route_path, $route_name); ?>
1812
public function index(): Response
1913
{
2014
<?php if ($with_template) { ?>

src/Resources/skeleton/crud/controller/Controller.tpl.php

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,7 @@
2121
<?php } ?>
2222
class <?= $class_name ?> extends <?= $parent_class_name; ?><?= "\n" ?>
2323
{
24-
<?php if ($use_attributes) { ?>
25-
#[Route('/', name: '<?= $route_name ?>_index', methods: ['GET'])]
26-
<?php } else { ?>
27-
/**
28-
* @Route("/", name="<?= $route_name ?>_index", methods={"GET"})
29-
*/
30-
<?php } ?>
24+
<?= $generator->generateRouteForControllerMethod('/', sprintf('%s_index', $route_name), ['GET']) ?>
3125
<?php if (isset($repository_full_class_name)): ?>
3226
public function index(<?= $repository_class_name ?> $<?= $repository_var ?>): Response
3327
{
@@ -48,13 +42,7 @@ public function index(): Response
4842
}
4943
<?php endif ?>
5044

51-
<?php if ($use_attributes) { ?>
52-
#[Route('/new', name: '<?= $route_name ?>_new', methods: ['GET', 'POST'])]
53-
<?php } else { ?>
54-
/**
55-
* @Route("/new", name="<?= $route_name ?>_new", methods={"GET","POST"})
56-
*/
57-
<?php } ?>
45+
<?= $generator->generateRouteForControllerMethod('/new', sprintf('%s_new', $route_name), ['GET', 'POST']) ?>
5846
public function new(Request $request): Response
5947
{
6048
$<?= $entity_var_singular ?> = new <?= $entity_class_name ?>();
@@ -82,27 +70,15 @@ public function new(Request $request): Response
8270
<?php } ?>
8371
}
8472

85-
<?php if ($use_attributes) { ?>
86-
#[Route('/{<?= $entity_identifier ?>}', name: '<?= $route_name ?>_show', methods: ['GET'])]
87-
<?php } else { ?>
88-
/**
89-
* @Route("/{<?= $entity_identifier ?>}", name="<?= $route_name ?>_show", methods={"GET"})
90-
*/
91-
<?php } ?>
73+
<?= $generator->generateRouteForControllerMethod(sprintf('/{%s}', $entity_identifier), sprintf('%s_show', $route_name), ['GET']) ?>
9274
public function show(<?= $entity_class_name ?> $<?= $entity_var_singular ?>): Response
9375
{
9476
return $this->render('<?= $templates_path ?>/show.html.twig', [
9577
'<?= $entity_twig_var_singular ?>' => $<?= $entity_var_singular ?>,
9678
]);
9779
}
9880

99-
<?php if ($use_attributes) { ?>
100-
#[Route('/{<?= $entity_identifier ?>}/edit', name: '<?= $route_name ?>_edit', methods: ['GET', 'POST'])]
101-
<?php } else { ?>
102-
/**
103-
* @Route("/{<?= $entity_identifier ?>}/edit", name="<?= $route_name ?>_edit", methods={"GET","POST"})
104-
*/
105-
<?php } ?>
81+
<?= $generator->generateRouteForControllerMethod(sprintf('/{%s}/edit', $entity_identifier), sprintf('%s_edit', $route_name), ['GET', 'POST']) ?>
10682
public function edit(Request $request, <?= $entity_class_name ?> $<?= $entity_var_singular ?>): Response
10783
{
10884
$form = $this->createForm(<?= $form_class_name ?>::class, $<?= $entity_var_singular ?>);
@@ -127,13 +103,7 @@ public function edit(Request $request, <?= $entity_class_name ?> $<?= $entity_va
127103
<?php } ?>
128104
}
129105

130-
<?php if ($use_attributes) { ?>
131-
#[Route('/{<?= $entity_identifier ?>}', name: '<?= $route_name ?>_delete', methods: ['POST'])]
132-
<?php } else { ?>
133-
/**
134-
* @Route("/{<?= $entity_identifier ?>}", name="<?= $route_name ?>_delete", methods={"POST"})
135-
*/
136-
<?php } ?>
106+
<?= $generator->generateRouteForControllerMethod(sprintf('/{%s}', $entity_identifier), sprintf('%s_delete', $route_name), ['POST']) ?>
137107
public function delete(Request $request, <?= $entity_class_name ?> $<?= $entity_var_singular ?>): Response
138108
{
139109
if ($this->isCsrfTokenValid('delete'.$<?= $entity_var_singular ?>->get<?= ucfirst($entity_identifier) ?>(), $request->request->get('_token'))) {

src/Resources/skeleton/registration/RegistrationController.tpl.php

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,7 @@ public function __construct(EmailVerifier $emailVerifier)
1515
}
1616

1717
<?php endif; ?>
18-
<?php if ($use_attributes) { ?>
19-
#[Route('<?= $route_path ?>', name: '<?= $route_name ?>')]
20-
<?php } else { ?>
21-
/**
22-
* @Route("<?= $route_path ?>", name="<?= $route_name ?>")
23-
*/
24-
<?php } ?>
18+
<?= $generator->generateRouteForControllerMethod($route_path, $route_name) ?>
2519
public function register(Request $request, UserPasswordEncoderInterface $passwordEncoder<?= $authenticator_full_class_name ? sprintf(', GuardAuthenticatorHandler $guardHandler, %s $authenticator', $authenticator_class_name) : '' ?>): Response
2620
{
2721
$user = new <?= $user_class_name ?>();
@@ -71,13 +65,7 @@ public function register(Request $request, UserPasswordEncoderInterface $passwor
7165
}
7266
<?php if ($will_verify_email): ?>
7367

74-
<?php if ($use_attributes) { ?>
75-
#[Route('/verify/email', name: 'app_verify_email')]
76-
<?php } else { ?>
77-
/**
78-
* @Route("/verify/email", name="app_verify_email")
79-
*/
80-
<?php } ?>
68+
<?= $generator->generateRouteForControllerMethod('/verify/email', 'app_verify_email') ?>
8169
public function verifyUserEmail(Request $request<?= $verify_email_anonymously ? sprintf(', %s %s', $repository_class_name, $repository_var) : null ?>): Response
8270
{
8371
<?php if (!$verify_email_anonymously): ?>

src/Util/TemplateComponentGenerator.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@
1818
*/
1919
final class TemplateComponentGenerator
2020
{
21+
private $phpCompatUtil;
22+
23+
public function __construct(PhpCompatUtil $phpCompatUtil)
24+
{
25+
$this->phpCompatUtil = $phpCompatUtil;
26+
}
27+
2128
public static function generateUseStatements(array $classesToBeImported): string
2229
{
2330
$transformed = [];
@@ -36,4 +43,48 @@ public static function generateUseStatements(array $classesToBeImported): string
3643

3744
return $statements;
3845
}
46+
47+
/** @legacy Annotation Support can be dropped w/ Symfony 6 LTS */
48+
public function generateRouteForControllerMethod(string $routePath, string $routeName, array $methods = [], bool $indent = true, bool $trailingNewLine = true): string
49+
{
50+
if ($this->phpCompatUtil->canUseAttributes()) {
51+
$attribute = sprintf('%s#[Route(\'%s\', name: \'%s\'', $indent ? ' ' : null, $routePath, $routeName);
52+
53+
if (!empty($methods)) {
54+
$attribute .= ', methods: [';
55+
56+
foreach ($methods as $method) {
57+
$attribute .= sprintf('\'%s\',', $method);
58+
}
59+
60+
$attribute = rtrim($attribute, ',');
61+
62+
$attribute .= ']';
63+
}
64+
65+
$attribute .= sprintf(')]%s', $trailingNewLine ? "\n" : null);
66+
67+
return $attribute;
68+
}
69+
70+
$annotation = sprintf('%s/**%s', $indent ? ' ' : null, "\n");
71+
$annotation .= sprintf('%s * @Route("%s", name="%s"', $indent ? ' ' : null, $routePath, $routeName);
72+
73+
if (!empty($methods)) {
74+
$annotation .= ', methods={';
75+
76+
foreach ($methods as $method) {
77+
$annotation .= sprintf('"%s",', $method);
78+
}
79+
80+
$annotation = rtrim($annotation, ',');
81+
82+
$annotation .= '}';
83+
}
84+
85+
$annotation .= sprintf(')%s', "\n");
86+
$annotation .= sprintf('%s */%s', $indent ? ' ' : null, $trailingNewLine ? "\n" : null);
87+
88+
return $annotation;
89+
}
3990
}

tests/Util/TemplateComponentGeneratorTest.php

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Bundle\MakerBundle\Tests\Util;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Bundle\MakerBundle\Util\PhpCompatUtil;
1516
use Symfony\Bundle\MakerBundle\Util\Sorter;
1617
use Symfony\Bundle\MakerBundle\Util\TemplateComponentGenerator;
1718

@@ -77,4 +78,132 @@ public function testComplexStatements(): void
7778
EOT;
7879
self::assertSame($expected, $result);
7980
}
81+
82+
public function testRouteAttributes(): void
83+
{
84+
$mockPhpCompatUtil = $this->createMock(PhpCompatUtil::class);
85+
$mockPhpCompatUtil
86+
->expects(self::once())
87+
->method('canUseAttributes')
88+
->willReturn(true)
89+
;
90+
91+
$generator = new TemplateComponentGenerator($mockPhpCompatUtil);
92+
93+
$expected = " #[Route('/', name: 'app_home')]\n";
94+
95+
self::assertSame($expected, $generator->generateRouteForControllerMethod('/', 'app_home'));
96+
}
97+
98+
public function testRouteAnnotations(): void
99+
{
100+
$mockPhpCompatUtil = $this->createMock(PhpCompatUtil::class);
101+
$mockPhpCompatUtil
102+
->expects(self::once())
103+
->method('canUseAttributes')
104+
->willReturn(false)
105+
;
106+
107+
$generator = new TemplateComponentGenerator($mockPhpCompatUtil);
108+
109+
$expected = " /**\n";
110+
$expected .= " * @Route(\"/\", name=\"app_home\")\n";
111+
$expected .= " */\n";
112+
113+
self::assertSame($expected, $generator->generateRouteForControllerMethod('/', 'app_home'));
114+
}
115+
116+
/**
117+
* @dataProvider routeMethodDataProvider
118+
*/
119+
public function testRouteMethods(string $expected, bool $useAttribute, array $methods): void
120+
{
121+
$mockPhpCompatUtil = $this->createMock(PhpCompatUtil::class);
122+
$mockPhpCompatUtil
123+
->expects(self::once())
124+
->method('canUseAttributes')
125+
->willReturn($useAttribute)
126+
;
127+
128+
$generator = new TemplateComponentGenerator($mockPhpCompatUtil);
129+
130+
if (!$useAttribute) {
131+
$annotation = " /**\n";
132+
$annotation .= $expected;
133+
$annotation .= " */\n";
134+
135+
$expected = $annotation;
136+
}
137+
138+
self::assertSame($expected, $generator->generateRouteForControllerMethod(
139+
'/',
140+
'app_home',
141+
$methods
142+
));
143+
}
144+
145+
public function routeMethodDataProvider(): \Generator
146+
{
147+
yield [" #[Route('/', name: 'app_home', methods: ['GET'])]\n", true, ['GET']];
148+
yield [" * @Route(\"/\", name=\"app_home\", methods={\"GET\"})\n", false, ['GET']];
149+
yield [" #[Route('/', name: 'app_home', methods: ['GET','POST'])]\n", true, ['GET', 'POST']];
150+
yield [" * @Route(\"/\", name=\"app_home\", methods={\"GET\",\"POST\"})\n", false, ['GET', 'POST']];
151+
}
152+
153+
/**
154+
* @dataProvider routeIndentationDataProvider
155+
*/
156+
public function testRouteIndentation(string $expected, bool $useAttribute): void
157+
{
158+
$mockPhpCompatUtil = $this->createMock(PhpCompatUtil::class);
159+
$mockPhpCompatUtil
160+
->expects(self::once())
161+
->method('canUseAttributes')
162+
->willReturn($useAttribute)
163+
;
164+
165+
$generator = new TemplateComponentGenerator($mockPhpCompatUtil);
166+
167+
self::assertSame($expected, $generator->generateRouteForControllerMethod(
168+
'/',
169+
'app_home',
170+
[],
171+
false
172+
));
173+
}
174+
175+
public function routeIndentationDataProvider(): \Generator
176+
{
177+
yield ["#[Route('/', name: 'app_home')]\n", true];
178+
yield ["/**\n * @Route(\"/\", name=\"app_home\")\n */\n", false];
179+
}
180+
181+
/**
182+
* @dataProvider routeTrailingNewLineDataProvider
183+
*/
184+
public function testRouteTrailingNewLine(string $expected, bool $useAttribute): void
185+
{
186+
$mockPhpCompatUtil = $this->createMock(PhpCompatUtil::class);
187+
$mockPhpCompatUtil
188+
->expects(self::once())
189+
->method('canUseAttributes')
190+
->willReturn($useAttribute)
191+
;
192+
193+
$generator = new TemplateComponentGenerator($mockPhpCompatUtil);
194+
195+
self::assertSame($expected, $generator->generateRouteForControllerMethod(
196+
'/',
197+
'app_home',
198+
[],
199+
false,
200+
false
201+
));
202+
}
203+
204+
public function routeTrailingNewLineDataProvider(): \Generator
205+
{
206+
yield ["#[Route('/', name: 'app_home')]", true];
207+
yield ["/**\n * @Route(\"/\", name=\"app_home\")\n */", false];
208+
}
80209
}

0 commit comments

Comments
 (0)