Skip to content

Commit c3eee5e

Browse files
authored
Merge pull request #1777 from Haehnchen/feature/route-navigation
support more use cases for php controller linemarker
2 parents b7f3e19 + 202674d commit c3eee5e

File tree

6 files changed

+146
-24
lines changed

6 files changed

+146
-24
lines changed

src/main/java/fr/adrienbrault/idea/symfony2plugin/routing/PhpLineMarkerProvider.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
import com.intellij.codeInsight.daemon.LineMarkerProvider;
55
import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo;
66
import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder;
7+
import com.intellij.lang.ASTNode;
78
import com.intellij.psi.PsiElement;
89
import com.intellij.psi.PsiFile;
910
import com.jetbrains.php.lang.psi.PhpFile;
10-
import com.jetbrains.php.lang.psi.elements.*;
11+
import com.jetbrains.php.lang.psi.elements.MethodReference;
1112
import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons;
1213
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
14+
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
1315
import fr.adrienbrault.idea.symfony2plugin.util.resource.FileResourceUtil;
1416
import org.jetbrains.annotations.NotNull;
1517
import org.jetbrains.annotations.Nullable;
@@ -46,20 +48,23 @@ public void collectSlowLineMarkers(@NotNull List<? extends PsiElement> psiElemen
4648
}
4749

4850
private void attachRouteActions(@NotNull Collection<? super LineMarkerInfo<?>> lineMarkerInfos, @NotNull PsiElement psiElement) {
49-
if (!(psiElement instanceof MethodReference)) {
51+
if (!(psiElement instanceof MethodReference) || !"controller".equalsIgnoreCase(((MethodReference) psiElement).getName()) || !PhpElementsUtil.isMethodReferenceInstanceOf((MethodReference) psiElement, "\\Symfony\\Component\\Routing\\Loader\\Configurator\\Traits\\RouteTrait", "controller")) {
5052
return;
5153
}
5254

5355
MethodReference methodCall = (MethodReference) psiElement;
54-
String controllerMethod = RouteHelper.getPhpController(methodCall);
5556

56-
PsiElement[] methods = RouteHelper.getMethodsOnControllerShortcut(psiElement.getProject(), controllerMethod);
57+
PsiElement[] methods = RouteHelper.getPhpController(methodCall);
5758
if(methods.length > 0) {
5859
NavigationGutterIconBuilder<PsiElement> builder = NavigationGutterIconBuilder.create(Symfony2Icons.TWIG_CONTROLLER_LINE_MARKER).
5960
setTargets(methods).
6061
setTooltipText("Navigate to action");
6162

62-
lineMarkerInfos.add(builder.createLineMarkerInfo(methodCall.getParameters()[0].getFirstChild()));
63+
// leaf elements are only allowed to attach; search "controller" psi element
64+
ASTNode nameNode = ((MethodReference) psiElement).getNameNode();
65+
if (nameNode != null) {
66+
lineMarkerInfos.add(builder.createLineMarkerInfo(nameNode.getPsi()));
67+
}
6368
}
6469
}
6570
}

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

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -838,29 +838,45 @@ public static String getYamlController(@NotNull YAMLKeyValue psiElement) {
838838
/**
839839
* Find controller definition in php function call.
840840
* $routes->controller('FooController:method');
841+
* $routes->controller([FooController::class, 'method']);
842+
* $routes->controller(FooController::class);
841843
*/
842-
@Nullable
843-
public static String getPhpController(@Nullable MethodReference methodCall) {
844-
if (methodCall == null || !methodCall.getName().equals("controller")) {
845-
return null;
846-
}
844+
@NotNull
845+
public static PsiElement[] getPhpController(@NotNull MethodReference methodCall) {
846+
PsiElement[] parameters = methodCall.getParameters();
847+
if (parameters.length == 1 && parameters[0] instanceof StringLiteralExpression) {
848+
// 'FooController::method'
849+
String contents = ((StringLiteralExpression) parameters[0]).getContents();
850+
if (StringUtils.isNotBlank(contents)) {
851+
return RouteHelper.getMethodsOnControllerShortcut(methodCall.getProject(), contents);
852+
}
847853

848-
PhpExpression expression = methodCall;
849-
while(expression instanceof MethodReference) {
850-
expression = (PhpExpression) expression.getFirstChild();
851-
}
854+
return new PsiElement[0];
855+
} else if (parameters.length == 1 && parameters[0] instanceof ClassConstantReference) {
856+
// FooController::class
857+
String classConstantPhpFqn = PhpElementsUtil.getClassConstantPhpFqn((ClassConstantReference) parameters[0]);
858+
if (StringUtils.isNotBlank(classConstantPhpFqn)) {
859+
return RouteHelper.getMethodsOnControllerShortcut(methodCall.getProject(), classConstantPhpFqn);
860+
}
852861

853-
var expr = expression.getInferredType();
862+
return new PsiElement[0];
863+
} else if (parameters.length == 1 && parameters[0] instanceof ArrayCreationExpression) {
864+
// [FooController::class, 'method']
865+
PsiElement[] elements = PhpElementsUtil.getArrayValues((ArrayCreationExpression) parameters[0]);
866+
if (elements.length == 2) {
867+
String className = PhpElementsUtil.getStringValue(elements[0]);
868+
if (StringUtils.isNotBlank(className)) {
869+
String method = PhpElementsUtil.getStringValue(elements[1]);
870+
if (method != null) {
871+
return RouteHelper.getMethodsOnControllerShortcut(methodCall.getProject(), className + "::" + method);
872+
}
873+
}
874+
}
854875

855-
if (!"\\Symfony\\Component\\Routing\\Loader\\Configurator\\RoutingConfigurator".equals(expr.toString())) {
856-
return null;
876+
return new PsiElement[0];
857877
}
858878

859-
PsiElement parameter = methodCall.getParameters()[0];
860-
if (parameter instanceof StringLiteralExpression) {
861-
return ((StringLiteralExpression) parameter).getContents();
862-
}
863-
return null;
879+
return new PsiElement[0];
864880
}
865881

866882
@Nullable

src/main/java/fr/adrienbrault/idea/symfony2plugin/util/PhpElementsUtil.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,33 @@ static public Map<String, PsiElement> getArrayValuesAsMap(@NotNull ArrayCreation
129129
return keys;
130130
}
131131

132+
/**
133+
* Get array values
134+
*
135+
* ["value", FOO::class] but not [$foo . $foo, $foo]
136+
*/
137+
@NotNull
138+
static public PsiElement[] getArrayValues(@NotNull ArrayCreationExpression arrayCreationExpression) {
139+
Collection<PsiElement> arrayValues = PhpPsiUtil.getChildren(arrayCreationExpression, psiElement ->
140+
psiElement.getNode().getElementType() == PhpElementTypes.ARRAY_VALUE
141+
);
142+
143+
List<PsiElement> items = new ArrayList<>();
144+
for (PsiElement child : arrayValues) {
145+
if (child instanceof PhpPsiElement) {
146+
PsiElement[] children = child.getChildren();
147+
if (children.length == 1) {
148+
items.add(children[0]);
149+
} else {
150+
// inalid for use: [$foo . $foo]
151+
return new PsiElement[0];
152+
}
153+
}
154+
}
155+
156+
return items.toArray(new PsiElement[0]);
157+
}
158+
132159
/**
133160
* array('foo' => FOO.class, 'foo1' => 'bar', 1 => 'foo')
134161
*/

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/routing/PhpLineMarkerProviderTest.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package fr.adrienbrault.idea.symfony2plugin.tests.routing;
22

3-
import com.intellij.patterns.XmlPatterns;
4-
import com.intellij.psi.PsiFile;
53
import com.jetbrains.php.lang.PhpFileType;
64
import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase;
75

@@ -17,6 +15,7 @@ public void setUp() throws Exception {
1715

1816
myFixture.copyFileToProject("BundleScopeLineMarkerProvider.php");
1917
myFixture.copyFileToProject("XmlLineMarkerProvider.php");
18+
myFixture.copyFileToProject("classes.php");
2019
}
2120

2221
protected String getTestDataPath() {
@@ -38,5 +37,35 @@ public void testThatRouteLineMarkerForControllerIsGiven() {
3837
),
3938
new LineMarker.ToolTipEqualsAssert("Navigate to action")
4039
);
40+
41+
assertLineMarker(
42+
myFixture.configureByText(
43+
PhpFileType.INSTANCE,
44+
"<?php\n" +
45+
"use Symfony\\Component\\Routing\\Loader\\Configurator\\RoutingConfigurator;\n" +
46+
"\n" +
47+
"return function(RoutingConfigurator $routes) {\n" +
48+
" $routes->add('xml_route', '/xml')\n" +
49+
" ->controller(\\Foo\\Bar::class)\n" +
50+
" ;\n" +
51+
"};"
52+
),
53+
new LineMarker.ToolTipEqualsAssert("Navigate to action")
54+
);
55+
56+
assertLineMarker(
57+
myFixture.configureByText(
58+
PhpFileType.INSTANCE,
59+
"<?php\n" +
60+
"use Symfony\\Component\\Routing\\Loader\\Configurator\\RoutingConfigurator;\n" +
61+
"\n" +
62+
"return function(RoutingConfigurator $routes) {\n" +
63+
" $routes->add('xml_route', '/xml')\n" +
64+
" ->controller([\\Foo\\Bar2::class, 'index'])\n" +
65+
" ;\n" +
66+
"};"
67+
),
68+
new LineMarker.ToolTipEqualsAssert("Navigate to action")
69+
);
4170
}
4271
}

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/routing/fixtures/XmlLineMarkerProvider.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,15 @@
44
{
55
class Bar
66
{
7+
public function __invoke()
8+
{
9+
}
10+
}
11+
12+
class Bar2
13+
{
14+
public function index()
15+
{
16+
}
717
}
818
}

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/routing/fixtures/classes.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,39 @@
44
{
55
abstract class UrlGenerator implements UrlGeneratorInterface {};
66
interface UrlGeneratorInterface {}
7+
}
8+
namespace Symfony\Component\Routing\Loader\Configurator
9+
{
10+
use Symfony\Component\Routing\Loader\Configurator\Traits\AddTrait;
11+
use Symfony\Component\Routing\Loader\Configurator\Traits\RouteTrait;
12+
13+
class RouteConfigurator
14+
{
15+
use AddTrait;
16+
use RouteTrait;
17+
}
18+
19+
class RoutingConfigurator
20+
{
21+
use AddTrait;
22+
}
23+
}
24+
25+
namespace Symfony\Component\Routing\Loader\Configurator\Traits
26+
{
27+
use Symfony\Component\Routing\Loader\Configurator\RouteConfigurator;
28+
29+
trait RouteTrait
30+
{
31+
final public function controller(callable|string|array $controller): static
32+
{
33+
}
34+
}
35+
36+
trait AddTrait
37+
{
38+
public function add(string $name, string|array $path): RouteConfigurator
39+
{
40+
}
41+
}
742
}

0 commit comments

Comments
 (0)