Skip to content

Commit e2dafb5

Browse files
committed
support Symfony 3.4 controller route names prefix in annotations #1017
1 parent 921b10c commit e2dafb5

File tree

9 files changed

+170
-48
lines changed

9 files changed

+170
-48
lines changed

src/fr/adrienbrault/idea/symfony2plugin/routing/RouteHelper.java

+26-4
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public class RouteHelper {
7171

7272
private static final Key<CachedValue<Map<String, Route>>> ROUTE_CACHE = new Key<>("SYMFONY:ROUTE_CACHE");
7373

74-
private static Set<String> ROUTE_CLASSES = new HashSet<>(Arrays.asList(
74+
public static Set<String> ROUTE_CLASSES = new HashSet<>(Arrays.asList(
7575
"Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\Route",
7676
"Symfony\\Component\\Routing\\Annotation\\Route"
7777
));
@@ -820,8 +820,10 @@ public static PsiElement getRouteNameTarget(@NotNull Project project, @NotNull S
820820
}
821821
} else if(psiFile instanceof PhpFile) {
822822
// find on @Route annotation
823-
824823
for (PhpClass phpClass : PhpPsiUtil.findAllClasses((PhpFile) psiFile)) {
824+
// get prefix by PhpClass
825+
String prefix = getRouteNamePrefix(phpClass);
826+
825827
for (Method method : phpClass.getOwnMethods()) {
826828
PhpDocComment docComment = method.getDocComment();
827829
if(docComment == null) {
@@ -840,13 +842,13 @@ public static PsiElement getRouteNameTarget(@NotNull Project project, @NotNull S
840842
String annotationRouteName = phpDocTagAnnotation.getPropertyValue("name");
841843
if(annotationRouteName != null) {
842844
// name provided @Route(name="foobar")
843-
if(routeName.equals(annotationRouteName)) {
845+
if(routeName.equals(prefix + annotationRouteName)) {
844846
return phpDocTagAnnotation.getPropertyValuePsi("name");
845847
}
846848
} else {
847849
// just @Route() without name provided
848850
String routeByMethod = AnnotationBackportUtil.getRouteByMethod(phpDocTagAnnotation.getPhpDocTag());
849-
if(routeName.equals(routeByMethod)) {
851+
if(routeName.equals(prefix + routeByMethod)) {
850852
return phpDocTagAnnotation.getPhpDocTag();
851853
}
852854
}
@@ -860,6 +862,26 @@ public static PsiElement getRouteNameTarget(@NotNull Project project, @NotNull S
860862
return null;
861863
}
862864

865+
/**
866+
* Extract route name of @Route(name="foobar_")
867+
* Must return empty string is easy for easier accessibility
868+
*/
869+
@NotNull
870+
private static String getRouteNamePrefix(@NotNull PhpClass phpClass) {
871+
PhpDocCommentAnnotation phpClassContainer = AnnotationUtil.getPhpDocCommentAnnotationContainer(phpClass.getDocComment());
872+
if(phpClassContainer != null) {
873+
PhpDocTagAnnotation firstPhpDocBlock = phpClassContainer.getFirstPhpDocBlock(ROUTE_CLASSES.toArray(new String[ROUTE_CLASSES.size()]));
874+
if(firstPhpDocBlock != null) {
875+
String name = firstPhpDocBlock.getPropertyValue("name");
876+
if(name != null && StringUtils.isNotBlank(name)) {
877+
return name;
878+
}
879+
}
880+
}
881+
882+
return "";
883+
}
884+
863885
@Nullable
864886
public static String getRouteUrl(Route route) {
865887

src/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/AnnotationRoutesStubIndex.java

+43-5
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ private static class MyPsiRecursiveElementWalkingVisitor extends PsiRecursiveEle
151151
private final Map<String, StubIndexedRoute> map;
152152
private Map<String, String> fileImports;
153153

154-
public MyPsiRecursiveElementWalkingVisitor(Map<String, StubIndexedRoute> map) {
154+
private MyPsiRecursiveElementWalkingVisitor(@NotNull Map<String, StubIndexedRoute> map) {
155155
this.map = map;
156156
}
157157

@@ -163,7 +163,7 @@ public void visitElement(PsiElement element) {
163163
super.visitElement(element);
164164
}
165165

166-
public void visitPhpDocTag(PhpDocTag phpDocTag) {
166+
private void visitPhpDocTag(@NotNull PhpDocTag phpDocTag) {
167167

168168
// "@var" and user non related tags dont need an action
169169
if(AnnotationBackportUtil.NON_ANNOTATION_TAGS.contains(phpDocTag.getName())) {
@@ -189,24 +189,29 @@ public void visitPhpDocTag(PhpDocTag phpDocTag) {
189189
return;
190190
}
191191

192-
String routeName = AnnotationBackportUtil.getAnnotationRouteName(phpDocAttributeList.getText());
192+
String routeName = AnnotationBackportUtil.getPropertyValue(phpDocTag, "name");
193193
if(routeName == null) {
194194
routeName = AnnotationBackportUtil.getRouteByMethod(phpDocTag);
195195
}
196196

197197
if(routeName != null && StringUtils.isNotBlank(routeName)) {
198+
// prepend route name on PhpClass scope
199+
String routeNamePrefix = getRouteNamePrefix(phpDocTag);
200+
if(routeNamePrefix != null) {
201+
routeName = routeNamePrefix + routeName;
202+
}
198203

199204
StubIndexedRoute route = new StubIndexedRoute(routeName);
200205

201206
String path = "";
202207

203-
// get class scope pattern
208+
// extract class path @Route("/foo") => "/foo" for prefixing upcoming methods
204209
String classPath = getClassRoutePattern(phpDocTag);
205210
if(classPath != null) {
206211
path += classPath;
207212
}
208213

209-
// extract method path
214+
// extract method path @Route("/foo") => "/foo"
210215
PhpPsiElement firstPsiChild = ((PhpPsiElement) phpDocAttributeList).getFirstPsiChild();
211216
if(firstPsiChild instanceof StringLiteralExpression) {
212217
String contents = ((StringLiteralExpression) firstPsiChild).getContents();
@@ -228,6 +233,39 @@ public void visitPhpDocTag(PhpDocTag phpDocTag) {
228233
}
229234
}
230235

236+
/**
237+
* Extract route name of parent class "@Route(name="foo_")"
238+
*/
239+
@Nullable
240+
private String getRouteNamePrefix(@NotNull PhpDocTag phpDocTag) {
241+
PhpClass phpClass = PsiTreeUtil.getParentOfType(phpDocTag, PhpClass.class);
242+
if (phpClass == null) {
243+
return null;
244+
}
245+
246+
PhpDocComment docComment = phpClass.getDocComment();
247+
if (docComment == null) {
248+
return null;
249+
}
250+
251+
for (PhpDocTag docTag : PsiTreeUtil.getChildrenOfTypeAsList(docComment, PhpDocTag.class)) {
252+
String annotationFqnName = AnnotationRoutesStubIndex.getClassNameReference(docTag, this.fileImports);
253+
254+
// check @Route or alias
255+
if(annotationFqnName == null || !RouteHelper.isRouteClassAnnotation(annotationFqnName)) {
256+
continue;
257+
}
258+
259+
// extract "name" property
260+
String annotationRouteName = AnnotationBackportUtil.getPropertyValue(docTag, "name");
261+
if(StringUtils.isNotBlank(annotationRouteName)) {
262+
return annotationRouteName;
263+
}
264+
}
265+
266+
return null;
267+
}
268+
231269
private void extractMethods(@NotNull PhpDocTag phpDocTag, @NotNull StubIndexedRoute route) {
232270
PsiElement phpDoc = phpDocTag.getParent();
233271
if(!(phpDoc instanceof PhpDocComment)) {

src/fr/adrienbrault/idea/symfony2plugin/stubs/indexes/PhpTwigTemplateUsageStubIndex.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ private void visitPhpDocTag(@NotNull PhpDocTag phpDocTag) {
122122
return;
123123
}
124124

125-
String template = AnnotationBackportUtil.getDefaultOrPropertyContents(phpDocTag, "template");
125+
String template = AnnotationBackportUtil.getPropertyValueOrDefault(phpDocTag, "template");
126126
if(template != null && template.endsWith(".html.twig")) {
127127
Method methodScope = AnnotationBackportUtil.getMethodScope(phpDocTag);
128128
if(methodScope != null) {

src/fr/adrienbrault/idea/symfony2plugin/util/AnnotationBackportUtil.java

+41-25
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020
import org.jetbrains.annotations.Nullable;
2121

2222
import java.util.*;
23-
import java.util.regex.Matcher;
24-
import java.util.regex.Pattern;
2523

2624
/**
2725
* Some method from Php Annotations plugin to not fully set a "depends" entry on it
@@ -148,25 +146,6 @@ public static PhpDocTag getReference(@Nullable PhpDocComment docComment, String
148146
return null;
149147
}
150148

151-
/**
152-
* Use AnnotationUtil of "PHP Annotations Plugin"
153-
*/
154-
@Nullable
155-
@Deprecated
156-
public static String getAnnotationRouteName(@Nullable String rawDocText) {
157-
158-
if(rawDocText == null) {
159-
return null;
160-
}
161-
162-
Matcher matcher = Pattern.compile("name\\s*=\\s*\"([\\w\\.-]+)\"").matcher(rawDocText);
163-
if (matcher.find()) {
164-
return matcher.group(1);
165-
}
166-
167-
return null;
168-
}
169-
170149
/**
171150
* Get class path on "use" path statement
172151
*/
@@ -256,11 +235,13 @@ public static Method getMethodScope(@NotNull PhpDocTag phpDocTag) {
256235
}
257236

258237
/**
238+
* Extract property value or fallback on default annotation pattern
239+
*
259240
* "@Template("foobar.html.twig")"
260241
* "@Template(template="foobar.html.twig")"
261242
*/
262243
@Nullable
263-
public static String getDefaultOrPropertyContents(@NotNull PhpDocTag phpDocTag, @NotNull String property) {
244+
public static String getPropertyValueOrDefault(@NotNull PhpDocTag phpDocTag, @NotNull String property) {
264245
PhpPsiElement attributeList = phpDocTag.getFirstPsiChild();
265246
if(attributeList == null || attributeList.getNode().getElementType() != PhpDocElementTypes.phpDocAttributeList) {
266247
return null;
@@ -278,9 +259,10 @@ public static String getDefaultOrPropertyContents(@NotNull PhpDocTag phpDocTag,
278259
contents = ((StringLiteralExpression) defaultValue).getContents();
279260
} else {
280261
// @Template(template="foobar.html.twig")
281-
PsiElement psiProperty = ContainerUtil.find(attributeList.getChildren(), psiElement ->
282-
getPropertyIdentifierValue(property).accepts(psiElement)
283-
);
262+
PsiElement psiProperty = Arrays.stream(attributeList.getChildren())
263+
.filter(psiElement1 -> getPropertyIdentifierValue(property).accepts(psiElement1))
264+
.findFirst()
265+
.orElse(null);
284266

285267
if(psiProperty instanceof StringLiteralExpression) {
286268
contents = ((StringLiteralExpression) psiProperty).getContents();
@@ -294,6 +276,40 @@ public static String getDefaultOrPropertyContents(@NotNull PhpDocTag phpDocTag,
294276
return contents;
295277
}
296278

279+
/**
280+
* Get the property value by given name
281+
*
282+
* "@Template(template="foobar.html.twig")"
283+
*/
284+
@Nullable
285+
public static String getPropertyValue(@NotNull PhpDocTag phpDocTag, @NotNull String property) {
286+
PhpPsiElement attributeList = phpDocTag.getFirstPsiChild();
287+
if(attributeList == null || attributeList.getNode().getElementType() != PhpDocElementTypes.phpDocAttributeList) {
288+
return null;
289+
}
290+
291+
PsiElement lParen = attributeList.getFirstChild();
292+
if(lParen == null) {
293+
return null;
294+
}
295+
296+
PsiElement psiProperty = Arrays.stream(attributeList.getChildren())
297+
.filter(psiElement1 -> getPropertyIdentifierValue(property).accepts(psiElement1))
298+
.findFirst()
299+
.orElse(null);
300+
301+
if(!(psiProperty instanceof StringLiteralExpression)) {
302+
return null;
303+
}
304+
305+
String contents = ((StringLiteralExpression) psiProperty).getContents();
306+
if(StringUtils.isNotBlank(contents)) {
307+
return contents;
308+
}
309+
310+
return null;
311+
}
312+
297313
/**
298314
* matches "@Callback(propertyName="<value>")"
299315
*/

tests/fr/adrienbrault/idea/symfony2plugin/tests/routing/RouteHelperTest.java

+11
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,17 @@ public void testGetRouteNameTarget() {
367367
assertTrue(element.getText().contains("my_car_foo_stuff_2"));
368368
}
369369

370+
/**
371+
* @see fr.adrienbrault.idea.symfony2plugin.routing.RouteHelper#getRouteNameTarget
372+
*/
373+
public void testGetRouteNameTargetForPrefixedControler() {
374+
PsiElement element = RouteHelper.getRouteNameTarget(getProject(), "foobar_myfoobar_apple_index");
375+
assertNotNull(element);
376+
377+
element = RouteHelper.getRouteNameTarget(getProject(), "foobar_my_foo");
378+
assertNotNull(element);
379+
}
380+
370381
/**
371382
* @see fr.adrienbrault.idea.symfony2plugin.routing.RouteHelper#isServiceController
372383
*/

tests/fr/adrienbrault/idea/symfony2plugin/tests/routing/fixtures/RouteHelper.php

+20
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,24 @@ public function foo2Action()
3838
{
3939
}
4040
}
41+
42+
/**
43+
* @Route("/foo_bar", name="foobar_")
44+
*/
45+
class AppleController
46+
{
47+
/**
48+
* @Route("/edit/{id}")
49+
*/
50+
public function indexAction()
51+
{
52+
}
53+
54+
/**
55+
* @Route("/edit/{id}", name="my_foo")
56+
*/
57+
public function fooAction()
58+
{
59+
}
60+
}
4161
}

tests/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/AnnotationRoutesStubIndexTest.java

+4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ public void testThatRouteWithInvokeMustNotAddAdditionalUnderscore() {
6969
assertIndexContains(AnnotationRoutesStubIndex.KEY, "my_post__invoke");
7070
}
7171

72+
public void testThatRouteWithPrefixIsInIndex() {
73+
assertIndexContains(AnnotationRoutesStubIndex.KEY, "foo_prefix_home");
74+
}
75+
7276
private RouteInterface getFirstValue(@NotNull String key) {
7377
return FileBasedIndexImpl.getInstance().getValues(AnnotationRoutesStubIndex.KEY, key, GlobalSearchScope.allScope(getProject())).get(0);
7478
}

tests/fr/adrienbrault/idea/symfony2plugin/tests/stubs/indexes/fixtures/AnnotationRoutesStubIndex.php

+20
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,26 @@ public function __invoke()
3535
}
3636
}
3737

38+
namespace MyPrefix
39+
{
40+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
41+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
42+
43+
/**
44+
* @Route("/foo", name="foo_")
45+
*/
46+
class PrefixController
47+
{
48+
/**
49+
* @Route("/edit/{id}", name="prefix_home")
50+
* @Method("GET")
51+
*/
52+
public function editAction()
53+
{
54+
}
55+
}
56+
}
57+
3858
namespace MyFooBarBundle\Controller
3959
{
4060
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

tests/fr/adrienbrault/idea/symfony2plugin/tests/util/AnnotationBackportUtilTest.java

+4-13
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,7 @@
77

88
public class AnnotationBackportUtilTest extends SymfonyLightCodeInsightFixtureTestCase {
99
/**
10-
* @see AnnotationBackportUtil#getAnnotationRouteName
11-
*/
12-
public void testAnnotationRouteName() {
13-
assertEquals("my_page_so_good", AnnotationBackportUtil.getAnnotationRouteName("\"/my/page/so-good\", name=\"my_page_so_good\""));
14-
assertEquals("my.page.so.good", AnnotationBackportUtil.getAnnotationRouteName("\"/my/page/so-good\", name=\"my.page.so.good\""));
15-
assertEquals("my-page.so_good", AnnotationBackportUtil.getAnnotationRouteName("\"/my/page/so-good\", name=\"my-page.so_good\""));
16-
}
17-
18-
/**
19-
* @see AnnotationBackportUtil#getDefaultOrPropertyContents
10+
* @see AnnotationBackportUtil#getPropertyValueOrDefault
2011
*/
2112
public void testGetDefaultOrProperty() {
2213
PhpDocTag fromText = PhpPsiElementFactory.createFromText(getProject(), PhpDocTag.class, "<?php\n" +
@@ -26,7 +17,7 @@ public void testGetDefaultOrProperty() {
2617
);
2718

2819
assertNotNull(fromText);
29-
assertEquals("foobar.html.twig", AnnotationBackportUtil.getDefaultOrPropertyContents(fromText, "foobar"));
20+
assertEquals("foobar.html.twig", AnnotationBackportUtil.getPropertyValueOrDefault(fromText, "foobar"));
3021

3122
fromText = PhpPsiElementFactory.createFromText(getProject(), PhpDocTag.class, "<?php\n" +
3223
"/**\n" +
@@ -35,7 +26,7 @@ public void testGetDefaultOrProperty() {
3526
);
3627

3728
assertNotNull(fromText);
38-
assertEquals("foobar.html.twig", AnnotationBackportUtil.getDefaultOrPropertyContents(fromText, "foobar"));
29+
assertEquals("foobar.html.twig", AnnotationBackportUtil.getPropertyValueOrDefault(fromText, "foobar"));
3930

4031
fromText = PhpPsiElementFactory.createFromText(getProject(), PhpDocTag.class, "<?php\n" +
4132
"/**\n" +
@@ -45,6 +36,6 @@ public void testGetDefaultOrProperty() {
4536
);
4637

4738
assertNotNull(fromText);
48-
assertEquals("foobar.html.twig", AnnotationBackportUtil.getDefaultOrPropertyContents(fromText, "foobar"));
39+
assertEquals("foobar.html.twig", AnnotationBackportUtil.getPropertyValueOrDefault(fromText, "foobar"));
4940
}
5041
}

0 commit comments

Comments
 (0)