55namespace Rector \Symfony \CodeQuality \Rector \ClassMethod ;
66
77use PhpParser \Node ;
8+ use PhpParser \Node \Attribute ;
89use PhpParser \Node \Expr ;
910use PhpParser \Node \Expr \Closure ;
1011use PhpParser \Node \Expr \MethodCall ;
2425use Rector \BetterPhpDocParser \PhpDocManipulator \PhpDocTagRemover ;
2526use Rector \Comments \NodeDocBlock \DocBlockUpdater ;
2627use Rector \Contract \PhpParser \Node \StmtsAwareInterface ;
28+ use Rector \Doctrine \NodeAnalyzer \AttributeFinder ;
2729use Rector \PhpParser \Node \BetterNodeFinder ;
2830use Rector \Rector \AbstractRector ;
2931use Rector \Symfony \Annotation \AnnotationAnalyzer ;
3739use Symplify \RuleDocGenerator \ValueObject \RuleDefinition ;
3840
3941/**
40- * @changelog https://github.com/symfony/symfony-docs/pull/12387#discussion_r329551967
41- * @changelog https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/view.html
42+ * @see https://github.com/symfony/symfony-docs/pull/12387#discussion_r329551967
43+ * @see https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/view.html
4244 *
4345 * @see \Rector\Symfony\Tests\CodeQuality\Rector\ClassMethod\TemplateAnnotationToThisRenderRector\TemplateAnnotationToThisRenderRectorTest
4446 */
@@ -54,6 +56,7 @@ public function __construct(
5456 private readonly DocBlockUpdater $ docBlockUpdater ,
5557 private readonly BetterNodeFinder $ betterNodeFinder ,
5658 private readonly PhpDocInfoFactory $ phpDocInfoFactory ,
59+ private readonly AttributeFinder $ attributeFinder ,
5760 ) {
5861 }
5962
@@ -119,15 +122,14 @@ public function refactor(Node $node): ?Node
119122 SymfonyAnnotation::TEMPLATE
120123 );
121124
122- foreach ($ node ->getMethods () as $ classMethod ) {
123- if (! $ classMethod ->isPublic ()) {
124- continue ;
125- }
125+ $ classTemplateAttribute = $ this ->attributeFinder ->findAttributeByClass ($ node , SymfonyAnnotation::TEMPLATE );
126126
127+ foreach ($ node ->getMethods () as $ classMethod ) {
127128 $ hasClassMethodChanged = $ this ->replaceTemplateAnnotation (
128129 $ classMethod ,
129- $ classDoctrineAnnotationTagValueNode
130+ $ classDoctrineAnnotationTagValueNode ?: $ classTemplateAttribute ,
130131 );
132+
131133 if ($ hasClassMethodChanged ) {
132134 $ hasChanged = true ;
133135 }
@@ -137,7 +139,7 @@ public function refactor(Node $node): ?Node
137139 return null ;
138140 }
139141
140- // cleanup Class_ @Template annotaion
142+ // cleanup Class_ @Template annotation
141143 if ($ classDoctrineAnnotationTagValueNode instanceof DoctrineAnnotationTagValueNode) {
142144 $ this ->removeDoctrineAnnotationTagValueNode ($ node , $ classDoctrineAnnotationTagValueNode );
143145 }
@@ -157,7 +159,7 @@ private function decorateAbstractControllerParentClass(Class_ $class): void
157159
158160 private function replaceTemplateAnnotation (
159161 ClassMethod $ classMethod ,
160- ? DoctrineAnnotationTagValueNode $ classDoctrineAnnotationTagValueNode
162+ DoctrineAnnotationTagValueNode | Attribute | null $ classTagValueNodeOrAttribute
161163 ): bool {
162164 if (! $ classMethod ->isPublic ()) {
163165 return false ;
@@ -168,28 +170,30 @@ private function replaceTemplateAnnotation(
168170 SymfonyAnnotation::TEMPLATE
169171 );
170172
171- if ($ doctrineAnnotationTagValueNode instanceof DoctrineAnnotationTagValueNode) {
172- return $ this ->refactorClassMethod ($ classMethod , $ doctrineAnnotationTagValueNode );
173+ $ templateAttribute = $ this ->attributeFinder ->findAttributeByClass ($ classMethod , SymfonyAnnotation::TEMPLATE );
174+
175+ if ($ doctrineAnnotationTagValueNode instanceof DoctrineAnnotationTagValueNode || $ templateAttribute instanceof Attribute) {
176+ return $ this ->refactorClassMethod ($ classMethod , $ doctrineAnnotationTagValueNode ?: $ templateAttribute );
173177 }
174178
175179 // global @Template access
176- if ($ classDoctrineAnnotationTagValueNode instanceof DoctrineAnnotationTagValueNode) {
177- return $ this ->refactorClassMethod ($ classMethod , $ classDoctrineAnnotationTagValueNode );
180+ if ($ classTagValueNodeOrAttribute instanceof DoctrineAnnotationTagValueNode || $ classTagValueNodeOrAttribute instanceof Attribute ) {
181+ return $ this ->refactorClassMethod ($ classMethod , $ classTagValueNodeOrAttribute );
178182 }
179183
180184 return false ;
181185 }
182186
183187 private function refactorClassMethod (
184188 ClassMethod $ classMethod ,
185- DoctrineAnnotationTagValueNode $ templateDoctrineAnnotationTagValueNode
189+ DoctrineAnnotationTagValueNode | Attribute $ templateTagValueNodeOrAttribute ,
186190 ): bool {
187191 $ hasThisRenderOrReturnsResponse = $ this ->hasLastReturnResponse ($ classMethod );
188192
189193 $ hasChanged = false ;
190194
191195 $ this ->traverseNodesWithCallable ($ classMethod , function (Node $ node ) use (
192- $ templateDoctrineAnnotationTagValueNode ,
196+ $ templateTagValueNodeOrAttribute ,
193197 $ hasThisRenderOrReturnsResponse ,
194198 $ classMethod ,
195199 &$ hasChanged
@@ -206,7 +210,7 @@ private function refactorClassMethod(
206210
207211 $ hasChangedNode = $ this ->refactorStmtsAwareNode (
208212 $ node ,
209- $ templateDoctrineAnnotationTagValueNode ,
213+ $ templateTagValueNodeOrAttribute ,
210214 $ hasThisRenderOrReturnsResponse ,
211215 $ classMethod
212216 );
@@ -223,11 +227,11 @@ private function refactorClassMethod(
223227
224228 $ thisRenderMethodCall = $ this ->thisRenderFactory ->create (
225229 null ,
226- $ templateDoctrineAnnotationTagValueNode ,
230+ $ templateTagValueNodeOrAttribute ,
227231 $ classMethod
228232 );
229233
230- $ this ->refactorNoReturn ($ classMethod , $ thisRenderMethodCall , $ templateDoctrineAnnotationTagValueNode );
234+ $ this ->refactorNoReturn ($ classMethod , $ thisRenderMethodCall , $ templateTagValueNodeOrAttribute );
231235
232236 return true ;
233237 }
@@ -254,7 +258,7 @@ private function hasLastReturnResponse(ClassMethod $classMethod): bool
254258
255259 private function refactorReturn (
256260 Return_ $ return ,
257- DoctrineAnnotationTagValueNode $ templateDoctrineAnnotationTagValueNode ,
261+ DoctrineAnnotationTagValueNode | Attribute $ templateTagValueNodeOrAttribute ,
258262 bool $ hasThisRenderOrReturnsResponse ,
259263 ClassMethod $ classMethod
260264 ): bool {
@@ -266,7 +270,7 @@ private function refactorReturn(
266270 // create "$this->render('template.file.twig.html', ['key' => 'value']);" method call
267271 $ thisRenderMethodCall = $ this ->thisRenderFactory ->create (
268272 $ return ,
269- $ templateDoctrineAnnotationTagValueNode ,
273+ $ templateTagValueNodeOrAttribute ,
270274 $ classMethod
271275 );
272276
@@ -275,7 +279,7 @@ private function refactorReturn(
275279 $ hasThisRenderOrReturnsResponse ,
276280 $ thisRenderMethodCall ,
277281 $ classMethod ,
278- $ templateDoctrineAnnotationTagValueNode
282+ $ templateTagValueNodeOrAttribute
279283 );
280284 }
281285
@@ -295,7 +299,7 @@ private function refactorReturnWithValue(
295299 bool $ hasThisRenderOrReturnsResponse ,
296300 MethodCall $ thisRenderMethodCall ,
297301 ClassMethod $ classMethod ,
298- DoctrineAnnotationTagValueNode $ doctrineAnnotationTagValueNode
302+ DoctrineAnnotationTagValueNode | Attribute $ doctrineTagValueNodeOrAttribute
299303 ): bool {
300304 /** @var Expr $lastReturnExpr */
301305 $ lastReturnExpr = $ return ->expr ;
@@ -324,24 +328,41 @@ private function refactorReturnWithValue(
324328 }
325329
326330 // already response
327- $ this ->removeDoctrineAnnotationTagValueNode ($ classMethod , $ doctrineAnnotationTagValueNode );
331+ $ this ->removeDoctrineAnnotationTagValueNode ($ classMethod , $ doctrineTagValueNodeOrAttribute );
328332 $ this ->returnTypeDeclarationUpdater ->updateClassMethod ($ classMethod , SymfonyClass::RESPONSE );
329333 return true ;
330334 }
331335
332336 private function removeDoctrineAnnotationTagValueNode (
333337 Class_ |ClassMethod $ node ,
334- DoctrineAnnotationTagValueNode $ doctrineAnnotationTagValueNode
338+ DoctrineAnnotationTagValueNode | Attribute $ doctrineTagValueNodeOrAttribute
335339 ): void {
336- $ phpDocInfo = $ this ->phpDocInfoFactory ->createFromNodeOrEmpty ($ node );
337- $ this ->phpDocTagRemover ->removeTagValueFromNode ($ phpDocInfo , $ doctrineAnnotationTagValueNode );
340+ if ($ doctrineTagValueNodeOrAttribute instanceof DoctrineAnnotationTagValueNode) {
341+ $ phpDocInfo = $ this ->phpDocInfoFactory ->createFromNodeOrEmpty ($ node );
342+ $ this ->phpDocTagRemover ->removeTagValueFromNode ($ phpDocInfo , $ doctrineTagValueNodeOrAttribute );
343+
344+ $ this ->docBlockUpdater ->updateRefactoredNodeWithPhpDocInfo ($ node );
345+
346+ return ;
347+ }
338348
339- $ this ->docBlockUpdater ->updateRefactoredNodeWithPhpDocInfo ($ node );
349+ foreach ($ node ->attrGroups as $ attrGroupKey => $ attrGroup ) {
350+ foreach ($ attrGroup ->attrs as $ attributeKey => $ attribute ) {
351+ if ($ attribute === $ doctrineTagValueNodeOrAttribute ) {
352+ unset($ attrGroup ->attrs [$ attributeKey ]);
353+ }
354+ }
355+
356+ // no attributes left? remove the whole dgroup
357+ if ($ attrGroup ->attrs === []) {
358+ unset($ node ->attrGroups [$ attrGroupKey ]);
359+ }
360+ }
340361 }
341362
342363 private function refactorStmtsAwareNode (
343364 StmtsAwareInterface $ stmtsAware ,
344- DoctrineAnnotationTagValueNode $ templateDoctrineAnnotationTagValueNode ,
365+ DoctrineAnnotationTagValueNode | Attribute $ templateTagValueNodeOrAttribute ,
345366 bool $ hasThisRenderOrReturnsResponse ,
346367 ClassMethod $ classMethod
347368 ): bool {
@@ -362,7 +383,7 @@ private function refactorStmtsAwareNode(
362383
363384 $ hasChangedReturn = $ this ->refactorReturn (
364385 $ stmt ,
365- $ templateDoctrineAnnotationTagValueNode ,
386+ $ templateTagValueNodeOrAttribute ,
366387 $ hasThisRenderOrReturnsResponse ,
367388 $ classMethod
368389 );
0 commit comments