Skip to content

Commit 2d6a8ca

Browse files
authored
Add Class_ annotation support to TemplateAnnotationToThisRenderRector (#666)
* add class-level fixture * Hook to Class_ node only to make easier to read
1 parent 49e82cc commit 2d6a8ca

File tree

4 files changed

+111
-30
lines changed

4 files changed

+111
-30
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace AppBundle\Controller;
4+
5+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
6+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
7+
8+
/**
9+
* @Template("items_template")
10+
*/
11+
final class ClassLevelAnnotation extends AbstractController
12+
{
13+
public function indexAction()
14+
{
15+
return [];
16+
}
17+
}
18+
19+
?>
20+
-----
21+
<?php
22+
23+
namespace AppBundle\Controller;
24+
25+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
26+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
27+
28+
final class ClassLevelAnnotation extends AbstractController
29+
{
30+
public function indexAction(): \Symfony\Component\HttpFoundation\Response
31+
{
32+
return $this->render('items_template');
33+
}
34+
}
35+
36+
?>

rules/CodeQuality/Rector/ClassMethod/TemplateAnnotationToThisRenderRector.php

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -98,65 +98,103 @@ public function indexAction()
9898
*/
9999
public function getNodeTypes(): array
100100
{
101-
return [ClassMethod::class, Class_::class];
101+
return [Class_::class];
102102
}
103103

104104
/**
105-
* @param Class_|ClassMethod $node
105+
* @param Class_ $node
106106
*/
107107
public function refactor(Node $node): ?Node
108108
{
109-
if ($node instanceof Class_) {
110-
return $this->addAbstractControllerParentClassIfMissing($node);
109+
if (! $this->annotationAnalyzer->hasClassMethodWithTemplateAnnotation($node)) {
110+
return null;
111111
}
112112

113-
return $this->replaceTemplateAnnotation($node);
114-
}
113+
$this->decorateAbstractControllerParentClass($node);
115114

116-
private function addAbstractControllerParentClassIfMissing(Class_ $class): ?Class_
117-
{
118-
if ($class->extends instanceof Name) {
119-
return null;
115+
$hasChanged = false;
116+
117+
$classDoctrineAnnotationTagValueNode = $this->annotationAnalyzer->getDoctrineAnnotationTagValueNode(
118+
$node,
119+
SymfonyAnnotation::TEMPLATE
120+
);
121+
122+
foreach ($node->getMethods() as $classMethod) {
123+
if (! $classMethod->isPublic()) {
124+
continue;
125+
}
126+
127+
$hasClassMethodChanged = $this->replaceTemplateAnnotation(
128+
$classMethod,
129+
$classDoctrineAnnotationTagValueNode
130+
);
131+
if ($hasClassMethodChanged) {
132+
$hasChanged = true;
133+
}
120134
}
121135

122-
if (! $this->annotationAnalyzer->hasClassMethodWithTemplateAnnotation($class)) {
136+
if (! $hasChanged) {
123137
return null;
124138
}
125139

126-
$class->extends = new FullyQualified('Symfony\Bundle\FrameworkBundle\Controller\AbstractController');
140+
// cleanup Class_ @Template annotaion
141+
if ($classDoctrineAnnotationTagValueNode instanceof DoctrineAnnotationTagValueNode) {
142+
$this->removeDoctrineAnnotationTagValueNode($node, $classDoctrineAnnotationTagValueNode);
143+
}
127144

128-
return $class;
145+
return $node;
129146
}
130147

131-
private function replaceTemplateAnnotation(ClassMethod $classMethod): ?ClassMethod
148+
private function decorateAbstractControllerParentClass(Class_ $class): void
132149
{
150+
if ($class->extends instanceof Name) {
151+
return;
152+
}
153+
154+
// this will make $this->render() method available
155+
$class->extends = new FullyQualified('Symfony\Bundle\FrameworkBundle\Controller\AbstractController');
156+
}
157+
158+
private function replaceTemplateAnnotation(
159+
ClassMethod $classMethod,
160+
?DoctrineAnnotationTagValueNode $classDoctrineAnnotationTagValueNode
161+
): bool {
133162
if (! $classMethod->isPublic()) {
134-
return null;
163+
return false;
135164
}
136165

137166
$doctrineAnnotationTagValueNode = $this->annotationAnalyzer->getDoctrineAnnotationTagValueNode(
138167
$classMethod,
139168
SymfonyAnnotation::TEMPLATE
140169
);
141170

142-
if (! $doctrineAnnotationTagValueNode instanceof DoctrineAnnotationTagValueNode) {
143-
return null;
171+
if ($doctrineAnnotationTagValueNode instanceof DoctrineAnnotationTagValueNode) {
172+
return $this->refactorClassMethod($classMethod, $doctrineAnnotationTagValueNode);
144173
}
145174

146-
return $this->refactorClassMethod($classMethod, $doctrineAnnotationTagValueNode);
175+
// global @Template access
176+
if ($classDoctrineAnnotationTagValueNode instanceof DoctrineAnnotationTagValueNode) {
177+
return $this->refactorClassMethod($classMethod, $classDoctrineAnnotationTagValueNode);
178+
}
179+
180+
return false;
147181
}
148182

149183
private function refactorClassMethod(
150184
ClassMethod $classMethod,
151185
DoctrineAnnotationTagValueNode $templateDoctrineAnnotationTagValueNode
152-
): ?ClassMethod {
186+
): bool {
153187
$hasThisRenderOrReturnsResponse = $this->hasLastReturnResponse($classMethod);
154188

189+
$hasChanged = false;
190+
155191
$this->traverseNodesWithCallable($classMethod, function (Node $node) use (
156192
$templateDoctrineAnnotationTagValueNode,
157193
$hasThisRenderOrReturnsResponse,
158-
$classMethod
194+
$classMethod,
195+
&$hasChanged
159196
): ?int {
197+
160198
// keep as similar type
161199
if ($node instanceof Closure || $node instanceof Function_) {
162200
return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
@@ -173,11 +211,13 @@ private function refactorClassMethod(
173211
$classMethod
174212
);
175213

214+
$hasChanged = true;
215+
176216
return null;
177217
});
178218

179219
if (! $this->emptyReturnNodeFinder->hasNoOrEmptyReturns($classMethod)) {
180-
return null;
220+
return $hasChanged;
181221
}
182222

183223
$thisRenderMethodCall = $this->thisRenderFactory->create(
@@ -188,7 +228,7 @@ private function refactorClassMethod(
188228

189229
$this->refactorNoReturn($classMethod, $thisRenderMethodCall, $templateDoctrineAnnotationTagValueNode);
190230

191-
return $classMethod;
231+
return true;
192232
}
193233

194234
private function hasLastReturnResponse(ClassMethod $classMethod): bool
@@ -288,13 +328,13 @@ private function refactorReturnWithValue(
288328
}
289329

290330
private function removeDoctrineAnnotationTagValueNode(
291-
ClassMethod $classMethod,
331+
Class_|ClassMethod $node,
292332
DoctrineAnnotationTagValueNode $doctrineAnnotationTagValueNode
293333
): void {
294-
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod);
334+
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node);
295335
$this->phpDocTagRemover->removeTagValueFromNode($phpDocInfo, $doctrineAnnotationTagValueNode);
296336

297-
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($classMethod);
337+
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node);
298338
}
299339

300340
private function refactorStmtsAwareNode(

rules/Configs/Rector/Class_/ParameterBagToAutowireAttributeRector.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ final class ParameterBagToAutowireAttributeRector extends AbstractRector impleme
3434
private const PARAMETER_BAG_CLASS = 'Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface';
3535

3636
public function __construct(
37-
private AutowiredParamFactory $autowiredParamFactory
37+
private readonly AutowiredParamFactory $autowiredParamFactory
3838
) {
3939
}
4040

src/Annotation/AnnotationAnalyzer.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,18 @@ public function __construct(
2020

2121
public function hasClassMethodWithTemplateAnnotation(Class_ $class): bool
2222
{
23+
$classTemplateAnnotation = $this->getDoctrineAnnotationTagValueNode($class, SymfonyAnnotation::TEMPLATE);
24+
if ($classTemplateAnnotation instanceof DoctrineAnnotationTagValueNode) {
25+
return true;
26+
}
27+
2328
foreach ($class->getMethods() as $classMethod) {
24-
$templateDoctrineAnnotationTagValueNode = $this->getDoctrineAnnotationTagValueNode(
29+
$classMethodTemplateAnnotation = $this->getDoctrineAnnotationTagValueNode(
2530
$classMethod,
2631
SymfonyAnnotation::TEMPLATE
2732
);
2833

29-
if ($templateDoctrineAnnotationTagValueNode instanceof DoctrineAnnotationTagValueNode) {
34+
if ($classMethodTemplateAnnotation instanceof DoctrineAnnotationTagValueNode) {
3035
return true;
3136
}
3237
}
@@ -35,10 +40,10 @@ public function hasClassMethodWithTemplateAnnotation(Class_ $class): bool
3540
}
3641

3742
public function getDoctrineAnnotationTagValueNode(
38-
ClassMethod $classMethod,
43+
Class_|ClassMethod $node,
3944
string $annotationClass
4045
): ?DoctrineAnnotationTagValueNode {
41-
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($classMethod);
46+
$phpDocInfo = $this->phpDocInfoFactory->createFromNode($node);
4247
if (! $phpDocInfo instanceof PhpDocInfo) {
4348
return null;
4449
}

0 commit comments

Comments
 (0)