From 8144d2c65c33f8961bdee7f3748bfd7a1b8da05b Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Sat, 6 Apr 2024 12:47:54 +0200 Subject: [PATCH] #2176 support route names on class level with __invoke method --- .../stubs/indexes/RoutesStubIndex.java | 2 +- .../AnnotationRouteElementVisitor.java | 72 ++++++++++++------- .../stubs/indexes/RoutesStubIndexTest.java | 10 +++ .../indexes/fixtures/RoutesStubIndex.php | 22 ++++++ 4 files changed, 80 insertions(+), 26 deletions(-) diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/RoutesStubIndex.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/RoutesStubIndex.java index a841adf3b..e9da32762 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/RoutesStubIndex.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/RoutesStubIndex.java @@ -78,7 +78,7 @@ public DataIndexer getIndexer() { } } } else if(psiFile instanceof PhpFile) { - // annotations: @Route() + // @Route(), #[Route] if(!isValidForIndex(inputData, psiFile)) { return map; } diff --git a/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/visitor/AnnotationRouteElementVisitor.java b/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/visitor/AnnotationRouteElementVisitor.java index 1ded1ef1d..27c049288 100644 --- a/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/visitor/AnnotationRouteElementVisitor.java +++ b/src/main/java/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/visitor/AnnotationRouteElementVisitor.java @@ -59,10 +59,19 @@ public void visitFile(@NotNull PhpClass phpClass) { PhpAttributesList childOfType = PsiTreeUtil.getChildOfType(method, PhpAttributesList.class); if (childOfType != null) { - visitPhpAttributesList(childOfType); + visitPhpAttributesList(childOfType, method, phpClass, false); + } + } + + Method invoke = phpClass.findOwnMethodByName("__invoke"); + if (invoke != null) { + PhpAttributesList childOfType = PsiTreeUtil.getChildOfType(phpClass, PhpAttributesList.class); + if (childOfType != null) { + visitPhpAttributesList(childOfType, invoke, phpClass, true); } } } + private void visitPhpDocTag(@NotNull PhpDocTag phpDocTag) { // "@var" and user non-related tags don't need an action @@ -132,32 +141,33 @@ private void visitPhpDocTag(@NotNull PhpDocTag phpDocTag) { } } - private void visitPhpAttributesList(@NotNull PhpAttributesList phpAttributesList) { + private void visitPhpAttributesList(@NotNull PhpAttributesList phpAttributesList, @NotNull Method method, @NotNull PhpClass phpClass, boolean classLevel) { PsiElement parent = phpAttributesList.getParent(); // prefix on class scope String routeNamePrefix = ""; String routePathPrefix = ""; - if (parent instanceof Method) { - PhpClass containingClass = ((Method) parent).getContainingClass(); - if (containingClass != null) { - for (PhpAttribute attribute : containingClass.getAttributes()) { - String fqn = attribute.getFQN(); - if(fqn == null || !RouteHelper.isRouteClassAnnotation(fqn)) { - continue; - } - String nameAttribute = PhpPsiAttributesUtil.getAttributeValueByNameAsString(attribute, 1, "name"); - if (nameAttribute != null) { - routeNamePrefix = nameAttribute; - } + for (PhpAttribute attribute : phpClass.getAttributes()) { + String fqn = attribute.getFQN(); + if(fqn == null || !RouteHelper.isRouteClassAnnotation(fqn)) { + continue; + } - String pathAttribute = PhpPsiAttributesUtil.getAttributeValueByNameAsStringWithDefaultParameterFallback(attribute, "path");; - if (pathAttribute != null) { - routePathPrefix = pathAttribute; - } - } + String nameAttribute = PhpPsiAttributesUtil.getAttributeValueByNameAsString(attribute, 1, "name"); + if (nameAttribute != null) { + routeNamePrefix = nameAttribute; } + + String pathAttribute = PhpPsiAttributesUtil.getAttributeValueByNameAsStringWithDefaultParameterFallback(attribute, "path");; + if (pathAttribute != null) { + routePathPrefix = pathAttribute; + } + } + + if (classLevel) { + routePathPrefix = ""; + routeNamePrefix = ""; } for (PhpAttribute attribute : phpAttributesList.getAttributes()) { @@ -168,13 +178,11 @@ private void visitPhpAttributesList(@NotNull PhpAttributesList phpAttributesList String nameAttribute = PhpPsiAttributesUtil.getAttributeValueByNameAsString(attribute, 1, "name"); - String routeName = null; + String routeName; if (nameAttribute != null) { routeName = nameAttribute; } else { - if (parent instanceof Method) { - routeName = AnnotationBackportUtil.getRouteByMethod((Method) parent); - } + routeName = AnnotationBackportUtil.getRouteByMethod(method); } if (routeName == null) { @@ -183,8 +191,10 @@ private void visitPhpAttributesList(@NotNull PhpAttributesList phpAttributesList StubIndexedRoute route = new StubIndexedRoute(routeNamePrefix + routeName); - if (parent instanceof Method) { - route.setController(getController((Method) parent)); + if (classLevel) { + route.setController(getController(phpClass)); + } else { + route.setController(getController(method)); } // find path "#[Route('/attributesWithoutName')]" or "#[Route(path: '/attributesWithoutName')]" @@ -304,6 +314,18 @@ private String getController(@NotNull Method method) { ); } + private String getController(@NotNull PhpClass phpClass) { + if(phpClass.findOwnMethodByName("__invoke") == null) { + return null; + } + + return String.format( + "%s::%s", + StringUtils.stripStart(phpClass.getFQN(), "\\"), + "__invoke" + ); + } + @Nullable private String getClassRoutePattern(@NotNull PhpDocTag phpDocTag) { PhpClass phpClass = PsiTreeUtil.getParentOfType(phpDocTag, PhpClass.class); diff --git a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/RoutesStubIndexTest.java b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/RoutesStubIndexTest.java index 21a8a6942..30ffe2964 100644 --- a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/RoutesStubIndexTest.java +++ b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/RoutesStubIndexTest.java @@ -198,6 +198,16 @@ public void testThatPhp8AttributesMethodsAreInIndex() { assertEquals("/foo-attributes/edit-not-named/{id}", route9.getPath()); } + public void testThatPhp8AttributesViaClassInvokeMethodsAreInIndex() { + RouteInterface route = getFirstValue("invoke_route_attribute"); + assertEquals("/foo-attributes", route.getPath()); + assertEquals("AttributeInvoke\\MyController::__invoke", route.getController()); + + RouteInterface route2 = getFirstValue("attributeinvoke_noname__invoke"); + assertEquals("/foo-attributes/no-name", route2.getPath()); + assertEquals("AttributeInvoke\\NoNameController::__invoke", route2.getController()); + } + @NotNull private RouteInterface getFirstValue(@NotNull String key) { return FileBasedIndex.getInstance().getValues(RoutesStubIndex.KEY, key, GlobalSearchScope.allScope(getProject())).get(0); diff --git a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/fixtures/RoutesStubIndex.php b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/fixtures/RoutesStubIndex.php index e93d639f0..2c80e361d 100644 --- a/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/fixtures/RoutesStubIndex.php +++ b/src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/fixtures/RoutesStubIndex.php @@ -265,6 +265,28 @@ public function emptyAttribute() } } + +namespace AttributeInvoke +{ + use Symfony\Component\Routing\Annotation\Route; + + #[Route(path: '/foo-attributes', name: 'invoke_route_attribute')] + class MyController + { + public function __invoke() + { + } + } + + #[Route(path: '/foo-attributes/no-name')] + class NoNameController + { + public function __invoke() + { + } + } +} + namespace Symfony\Component\Routing\Annotation { class Route