Skip to content

Commit 7c5cef3

Browse files
committed
use nested php namespace index for controller action collector instead of filesystem and provide testing
1 parent 0165ae5 commit 7c5cef3

File tree

4 files changed

+144
-29
lines changed

4 files changed

+144
-29
lines changed

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.jetbrains.php.lang.psi.elements.PhpClass;
99
import com.jetbrains.php.lang.psi.elements.PhpNamespace;
1010
import com.jetbrains.php.lang.psi.stubs.indexes.PhpNamespaceIndex;
11+
import org.jetbrains.annotations.NotNull;
1112

1213
import java.util.ArrayList;
1314
import java.util.Collection;
@@ -21,11 +22,13 @@ public class PhpIndexUtil {
2122
* @param namespaceName namespace name should start with \ and not end with "\"
2223
* @return classes inside namespace and sub-namespace
2324
*/
24-
public static Collection<PhpClass> getPhpClassInsideNamespace(Project project, String namespaceName) {
25+
@NotNull
26+
public static Collection<PhpClass> getPhpClassInsideNamespace(@NotNull Project project, @NotNull String namespaceName) {
2527
return getPhpClassInsideNamespace(project, PhpIndex.getInstance(project), namespaceName, 10);
2628
}
2729

28-
private static Collection<PhpClass> getPhpClassInsideNamespace(Project project, PhpIndex phpIndex, String namespaceName, int maxDeep) {
30+
@NotNull
31+
private static Collection<PhpClass> getPhpClassInsideNamespace(@NotNull Project project, @NotNull PhpIndex phpIndex, @NotNull String namespaceName, int maxDeep) {
2932

3033
final Collection<PhpClass> phpClasses = new ArrayList<PhpClass>();
3134

@@ -48,7 +51,7 @@ public boolean process(PhpNamespace phpNamespace) {
4851
return phpClasses;
4952
}
5053

51-
public static boolean hasNamespace(Project project, String namespaceName) {
54+
public static boolean hasNamespace(@NotNull Project project, @NotNull String namespaceName) {
5255

5356
if(!namespaceName.startsWith("\\")) {
5457
namespaceName = "\\" + namespaceName;

src/fr/adrienbrault/idea/symfony2plugin/util/controller/ControllerIndex.java

+47-25
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import fr.adrienbrault.idea.symfony2plugin.routing.Route;
1212
import fr.adrienbrault.idea.symfony2plugin.routing.RouteHelper;
1313
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
14+
import fr.adrienbrault.idea.symfony2plugin.util.PhpIndexUtil;
1415
import fr.adrienbrault.idea.symfony2plugin.util.SymfonyBundleUtil;
1516
import fr.adrienbrault.idea.symfony2plugin.util.dict.SymfonyBundle;
1617
import fr.adrienbrault.idea.symfony2plugin.util.service.ServiceXmlParserFactory;
@@ -28,18 +29,16 @@ public ControllerIndex(Project project) {
2829
this.phpIndex = PhpIndex.getInstance(project);
2930
}
3031

31-
public ArrayList<ControllerAction> getAction() {
32+
public List<ControllerAction> getActions() {
3233

33-
ArrayList<ControllerAction> actions = new ArrayList<ControllerAction>();
34+
List<ControllerAction> actions = new ArrayList<ControllerAction>();
3435
SymfonyBundleUtil symfonyBundleUtil = new SymfonyBundleUtil(phpIndex);
3536

36-
Collection<PhpClass> controllerClasses = phpIndex.getAllSubclasses("\\Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller");
37-
for(PhpClass controllerClass : controllerClasses) {
38-
actions.addAll(this.getActionMethods(symfonyBundleUtil, controllerClass));
37+
for (SymfonyBundle symfonyBundle : symfonyBundleUtil.getBundles()) {
38+
actions.addAll(this.getActionMethods(symfonyBundle));
3939
}
4040

4141
return actions;
42-
4342
}
4443

4544
@Nullable
@@ -71,7 +70,7 @@ public ControllerAction getControllerActionOnService(String shortcutName) {
7170

7271
@Nullable
7372
public ControllerAction getControllerAction(String shortcutName) {
74-
for(ControllerAction controllerAction: this.getAction()) {
73+
for(ControllerAction controllerAction: this.getActions()) {
7574
if(controllerAction.getShortcutName().equals(shortcutName)) {
7675
return controllerAction;
7776
}
@@ -80,27 +79,50 @@ public ControllerAction getControllerAction(String shortcutName) {
8079
return null;
8180
}
8281

83-
private ArrayList<ControllerAction> getActionMethods(SymfonyBundleUtil symfonyBundleUtil, PhpClass controllerClass) {
84-
85-
ArrayList<ControllerAction> actions = new ArrayList<ControllerAction>();
86-
87-
Collection<Method> methods = controllerClass.getMethods();
88-
SymfonyBundle symfonyBundle = symfonyBundleUtil.getContainingBundle(controllerClass);
82+
private List<ControllerAction> getActionMethods(SymfonyBundle symfonyBundle) {
8983

90-
if(symfonyBundle == null) {
91-
return actions;
84+
String namespaceName = symfonyBundle.getNamespaceName();
85+
if(!namespaceName.startsWith("\\")) {
86+
namespaceName = "\\" + namespaceName;
9287
}
9388

94-
String path = symfonyBundle.getRelative(controllerClass.getContainingFile().getVirtualFile(), true);
95-
if(path == null || !path.startsWith("Controller/") || !path.endsWith("Controller")) {
96-
return actions;
89+
if(!namespaceName.endsWith("\\")) {
90+
namespaceName += "\\";
9791
}
9892

99-
for(Method method : methods) {
100-
String methodName = method.getName();
101-
if(methodName.endsWith("Action") && method.getAccess().isPublic()) {
102-
String shortcutName = symfonyBundle.getName() + ":" + path.substring("Controller/".length(), path.length() - 10) + ':' + methodName.substring(0, methodName.length() - 6);
103-
actions.add(new ControllerAction(shortcutName, method));
93+
namespaceName += "Controller";
94+
95+
List<ControllerAction> actions = new ArrayList<ControllerAction>();
96+
97+
for (PhpClass phpClass : PhpIndexUtil.getPhpClassInsideNamespace(this.project, namespaceName)) {
98+
99+
if(!phpClass.getName().endsWith("Controller")) {
100+
continue;
101+
}
102+
103+
String presentableFQN = phpClass.getPresentableFQN();
104+
if(presentableFQN == null) {
105+
continue;
106+
}
107+
108+
if(!presentableFQN.startsWith("\\")) {
109+
presentableFQN = "\\" + presentableFQN;
110+
}
111+
112+
presentableFQN = presentableFQN.substring(0, presentableFQN.length() - "Controller".length());
113+
if(presentableFQN.length() == 0) {
114+
continue;
115+
}
116+
117+
String ns = presentableFQN.substring(namespaceName.length() + 1);
118+
119+
for(Method method : phpClass.getMethods()) {
120+
String methodName = method.getName();
121+
if(methodName.endsWith("Action") && method.getAccess().isPublic()) {
122+
String shortcutName = symfonyBundle.getName() + ":" + ns.replace("\\", "/") + ':' + methodName.substring(0, methodName.length() - 6);
123+
actions.add(new ControllerAction(shortcutName, method));
124+
}
125+
104126
}
105127

106128
}
@@ -169,10 +191,10 @@ public Method resolveShortcutName(String controllerName) {
169191
}
170192

171193
static public List<LookupElement> getControllerLookupElements(Project project) {
172-
ArrayList<LookupElement> lookupElements = new ArrayList<LookupElement>();
194+
List<LookupElement> lookupElements = new ArrayList<LookupElement>();
173195

174196
ControllerIndex controllerIndex = new ControllerIndex(project);
175-
for(ControllerAction controllerAction: controllerIndex.getAction()) {
197+
for(ControllerAction controllerAction: controllerIndex.getActions()) {
176198
lookupElements.add(new ControllerActionLookupElement(controllerAction));
177199
}
178200

tests/fr/adrienbrault/idea/symfony2plugin/tests/SymfonyLightCodeInsightFixtureTestCase.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public void assertNavigationContains(PsiElement psiElement, String targetShortcu
107107
}
108108

109109
if(!classTargets.contains(targetShortcut)) {
110-
fail(String.format("failed that PsiElement (%s) navigate to %s", psiElement.toString(), targetShortcut));
110+
fail(String.format("failed that PsiElement (%s) navigate to %s on %s", psiElement.toString(), targetShortcut, classTargets.toString()));
111111
}
112112

113113
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package fr.adrienbrault.idea.symfony2plugin.tests.routing;
2+
3+
import com.intellij.ide.highlighter.XmlFileType;
4+
import fr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase;
5+
import org.jetbrains.yaml.YAMLFileType;
6+
7+
/**
8+
* @author Daniel Espendiller <daniel@espendiller.net>
9+
* @see fr.adrienbrault.idea.symfony2plugin.util.controller.ControllerIndex
10+
*/
11+
public class RoutingDefinitionTest extends SymfonyLightCodeInsightFixtureTestCase {
12+
13+
public void setUp() throws Exception {
14+
super.setUp();
15+
16+
myFixture.configureByText("classes.php", "<?php\n" +
17+
"namespace AppBundle;\n" +
18+
"class AppBundle extends \\Symfony\\Component\\HttpKernel\\Bundle\\Bundle {}" +
19+
20+
"namespace AppBundle\\Controller;\n" +
21+
"class DefaultController {\n" +
22+
" public function indexAction() {}\n" +
23+
" private function fooAction() {}\n" +
24+
" public function indexActionFoo() {}\n" +
25+
"}" +
26+
27+
"namespace AppBundle\\Controller\\Foo;\n" +
28+
"class DefaultController {\n" +
29+
" public function indexAction() {}\n" +
30+
"}"
31+
);
32+
}
33+
34+
public void testYamlCompletion() {
35+
36+
assertCompletionContains(YAMLFileType.YML, "foo:\n" +
37+
" pattern: /\n" +
38+
" defaults: { _controller: <caret> }\n"
39+
, "AppBundle:Default:index", "AppBundle:Foo/Default:index"
40+
);
41+
42+
assertCompletionNotContains(YAMLFileType.YML, "foo:\n" +
43+
" pattern: /\n" +
44+
" defaults: { _controller: <caret> }\n"
45+
, "AppBundle:Default:foo"
46+
);
47+
48+
assertCompletionContains(YAMLFileType.YML, "foo:\n" +
49+
" pattern: /\n" +
50+
" defaults: { _controller: '<caret>' }\n"
51+
, "AppBundle:Default:index", "AppBundle:Foo/Default:index"
52+
);
53+
54+
assertCompletionNotContains(YAMLFileType.YML, "foo:\n" +
55+
" pattern: /\n" +
56+
" defaults: { _controller: \"<caret>\" }\n"
57+
, "AppBundle:Default:foo"
58+
);
59+
60+
}
61+
62+
63+
public void testYamlNavigation() {
64+
65+
assertNavigationContains(YAMLFileType.YML, "foo:\n" +
66+
" pattern: /\n" +
67+
" defaults: { _controller: AppBundle:<caret>Default:index }\n"
68+
, "AppBundle\\Controller\\DefaultController::indexAction"
69+
);
70+
71+
assertNavigationContains(YAMLFileType.YML, "foo:\n" +
72+
" pattern: /\n" +
73+
" defaults: { _controller: AppBundle:Foo/<caret>Default:index }\n"
74+
, "AppBundle\\Controller\\Foo\\DefaultController::indexAction"
75+
);
76+
77+
}
78+
79+
public void testXmlCompletion() {
80+
81+
assertCompletionContains(XmlFileType.INSTANCE, "" +
82+
"<route id=\"blog_show\" path=\"/blog/{slug}\">\n" +
83+
" <default key=\"_controller\"><caret></default>\n" +
84+
"</route>"
85+
, "AppBundle:Default:index", "AppBundle:Foo/Default:index"
86+
);
87+
88+
}
89+
90+
}

0 commit comments

Comments
 (0)