Skip to content

Commit 71b0339

Browse files
committed
provide support for compiled Symfony 4 / 5 route names
1 parent 5e0a4d4 commit 71b0339

File tree

5 files changed

+162
-12
lines changed

5 files changed

+162
-12
lines changed

src/main/java/fr/adrienbrault/idea/symfony2plugin/Settings.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public class Settings implements PersistentStateComponent<Settings> {
3030
"var/cache/dev/appDevDebugProjectContainer.xml",
3131
"var/cache/dev/srcDevDebugProjectContainer.xml",
3232
"var/cache/dev/srcApp_KernelDevDebugContainer.xml",
33+
"var/cache/dev/App_KernelDevDebugContainer.xml" // Symfony => 4 + flex
3334
};
3435

3536
// Default Symfony 2, 3 and 4 paths
@@ -38,6 +39,7 @@ public class Settings implements PersistentStateComponent<Settings> {
3839
"var/cache/dev/appDevUrlGenerator.php",
3940
"var/cache/dev/appDevDebugProjectContainerUrlGenerator.php",
4041
"var/cache/dev/srcDevDebugProjectContainerUrlGenerator.php",
42+
"var/cache/dev/url_matching_routes.php", // Symfony >= 4
4143
};
4244

4345
public static String DEFAULT_TRANSLATION_PATH = "app/cache/dev/translations";

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

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -247,14 +247,6 @@ public static ControllerClassOnShortcutReturn getControllerClassOnShortcut(@NotN
247247
return null;
248248
}
249249

250-
private static <E> ArrayList<E> makeCollection(Iterable<E> iter) {
251-
ArrayList<E> list = new ArrayList<>();
252-
for (E item : iter) {
253-
list.add(item);
254-
}
255-
return list;
256-
}
257-
258250
private static String getPath(Project project, String path) {
259251
if (!FileUtil.isAbsolute(path)) { // Project relative path
260252
path = project.getBasePath() + "/" + path;
@@ -344,7 +336,6 @@ public static Map<String, Route> getRoutesInsideUrlGeneratorFile(@NotNull Projec
344336
return getRoutesInsideUrlGeneratorFile(psiFile);
345337
}
346338

347-
348339
/**
349340
* Temporary or remote files dont support "isInstanceOf", check for string implementation first
350341
*/
@@ -368,9 +359,30 @@ private static boolean isRouteClass(@NotNull PhpClass phpClass) {
368359

369360
@NotNull
370361
public static Map<String, Route> getRoutesInsideUrlGeneratorFile(@NotNull PsiFile psiFile) {
371-
372362
Map<String, Route> routes = new HashMap<>();
373363

364+
// Symfony >= 4
365+
// extract the routes on a return statement
366+
// return [['route'] => [...]]
367+
for (PhpReturn phpReturn : PsiTreeUtil.findChildrenOfType(psiFile, PhpReturn.class)) {
368+
PsiElement argument = phpReturn.getArgument();
369+
if (!(argument instanceof ArrayCreationExpression)) {
370+
continue;
371+
}
372+
373+
// get only the inside arrays
374+
// [[..], [..]] => [..], [..]
375+
for (Map.Entry<String, PsiElement> routeArray : PhpElementsUtil.getArrayKeyValueMapWithValueAsPsiElement((ArrayCreationExpression) argument).entrySet()) {
376+
List<ArrayCreationExpression> routeArrayOptions = new ArrayList<>();
377+
for (PhpPsiElement routeOption : PsiTreeUtil.getChildrenOfTypeAsList(routeArray.getValue(), PhpPsiElement.class)) {
378+
routeArrayOptions.add(PsiTreeUtil.getChildOfType(routeOption, ArrayCreationExpression.class));
379+
}
380+
381+
routes.put(routeArray.getKey(), convertRouteConfigForReturnArray(routeArray.getKey(), routeArrayOptions));
382+
}
383+
}
384+
385+
// Symfony < 4
374386
// heavy stuff here, to get nested routing array :)
375387
// list($variables, $defaults, $requirements, $tokens, $hostTokens)
376388
Collection<PhpClass> phpClasses = PsiTreeUtil.findChildrenOfType(psiFile, PhpClass.class);
@@ -454,9 +466,60 @@ private static void collectRoutesOnArrayCreation(@NotNull Map<String, Route> rou
454466
}
455467
}
456468

469+
/**
470+
* Used in Symfony > 4 where routes are wrapped into a return array
471+
*/
472+
@NotNull
473+
private static Route convertRouteConfigForReturnArray(@NotNull String routeName, @NotNull List<ArrayCreationExpression> hashElementCollection) {
474+
Set<String> variables = new HashSet<>();
475+
if(hashElementCollection.size() >= 1 && hashElementCollection.get(0) != null) {
476+
ArrayCreationExpression value = hashElementCollection.get(0);
477+
if(value != null) {
478+
variables.addAll(PhpElementsUtil.getArrayValuesAsString(value));
479+
}
480+
}
481+
482+
Map<String, String> defaults = new HashMap<>();
483+
if(hashElementCollection.size() >= 2 && hashElementCollection.get(1) != null) {
484+
ArrayCreationExpression value = hashElementCollection.get(1);
485+
if(value != null) {
486+
defaults = PhpElementsUtil.getArrayKeyValueMap(value);
487+
}
488+
}
489+
490+
Map<String, String>requirements = new HashMap<>();
491+
if(hashElementCollection.size() >= 3 && hashElementCollection.get(2) != null) {
492+
ArrayCreationExpression value = hashElementCollection.get(2);
493+
if(value != null) {
494+
requirements = PhpElementsUtil.getArrayKeyValueMap(value);
495+
}
496+
}
497+
498+
List<Collection<String>> tokens = new ArrayList<>();
499+
if(hashElementCollection.size() >= 4 && hashElementCollection.get(3) != null) {
500+
ArrayCreationExpression tokenArray = hashElementCollection.get(3);
501+
if(tokenArray != null) {
502+
for(ArrayHashElement tokenArrayConfig: tokenArray.getHashElements()) {
503+
if(tokenArrayConfig.getValue() instanceof ArrayCreationExpression) {
504+
Map<String, String> arrayKeyValueMap = PhpElementsUtil.getArrayKeyValueMap((ArrayCreationExpression) tokenArrayConfig.getValue());
505+
tokens.add(arrayKeyValueMap.values());
506+
}
507+
}
508+
}
509+
510+
}
511+
512+
// hostTokens = 4 need them?
513+
return new Route(routeName, variables, defaults, requirements, tokens);
514+
}
515+
516+
/**
517+
* Used in Symfony < 4 where routes are wrapped into a class
518+
*/
457519
@NotNull
458520
private static Route convertRouteConfig(@NotNull String routeName, @NotNull ArrayCreationExpression hashValue) {
459-
List<ArrayHashElement> hashElementCollection = makeCollection(hashValue.getHashElements());
521+
List<ArrayHashElement> hashElementCollection = new ArrayList<>();
522+
hashValue.getHashElements().forEach(hashElementCollection::add);
460523

461524
Set<String> variables = new HashSet<>();
462525
if(hashElementCollection.size() >= 1 && hashElementCollection.get(0).getValue() instanceof ArrayCreationExpression) {

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,43 @@ static public Map<String, PsiElement> getArrayValuesAsMap(@NotNull ArrayCreation
115115
Map<String, PsiElement> keys = new HashMap<>();
116116
for (PsiElement child : arrayValues) {
117117
String stringValue = PhpElementsUtil.getStringValue(child.getFirstChild());
118-
if(stringValue != null && StringUtils.isNotBlank(stringValue)) {
118+
if(StringUtils.isNotBlank(stringValue)) {
119119
keys.put(stringValue, child);
120120
}
121121
}
122122

123123
return keys;
124124
}
125125

126+
/**
127+
* array('foo' => FOO.class, 'foo1' => 'bar', 1 => 'foo')
128+
*/
129+
@NotNull
130+
static public Map<String, PsiElement> getArrayKeyValueMapWithValueAsPsiElement(@NotNull ArrayCreationExpression arrayCreationExpression) {
131+
HashMap<String, PsiElement> keys = new HashMap<>();
132+
133+
for(ArrayHashElement arrayHashElement: arrayCreationExpression.getHashElements()) {
134+
PhpPsiElement child = arrayHashElement.getKey();
135+
if(child != null && ((child instanceof StringLiteralExpression) || PhpPatterns.psiElement(PhpElementTypes.NUMBER).accepts(child))) {
136+
137+
String key;
138+
if(child instanceof StringLiteralExpression) {
139+
key = ((StringLiteralExpression) child).getContents();
140+
} else {
141+
key = child.getText();
142+
}
143+
144+
if(key == null || StringUtils.isBlank(key)) {
145+
continue;
146+
}
147+
148+
keys.put(key, arrayHashElement.getValue());
149+
}
150+
}
151+
152+
return keys;
153+
}
154+
126155
/**
127156
* array('foo' => 'bar', 'foo1' => 'bar', 1 => 'foo')
128157
*/

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,23 @@ public void testGetRoutesInsideUrlGeneratorFile() {
264264
assertNull(routes.get("_assetic_91dd2a8"));
265265
}
266266

267+
/**
268+
* @see fr.adrienbrault.idea.symfony2plugin.routing.RouteHelper#getRoutesInsideUrlGeneratorFile
269+
*/
270+
public void testGetRoutesInsideUrlGeneratorFileUrlGeneratorRoutes() {
271+
Map<String, Route> routes = RouteHelper.getRoutesInsideUrlGeneratorFile(getProject(), myFixture.copyFileToProject("url_generating_routes.php"));
272+
273+
Route previewError = routes.get("_preview_error");
274+
assertEquals("error_controller::preview", previewError.getController());
275+
assertContainsElements(previewError.getDefaults().keySet(), "_format", "_controller");
276+
assertContainsElements(previewError.getRequirements().keySet(), "code");
277+
assertContainsElements(previewError.getVariables(), "code", "_format");
278+
279+
Route profiler = routes.get("_profiler");
280+
assertEquals("web_profiler.controller.profiler::panelAction", profiler.getController());
281+
assertContainsElements(profiler.getVariables(), "token");
282+
}
283+
267284
/**
268285
* @see fr.adrienbrault.idea.symfony2plugin.routing.RouteHelper#convertMethodToRouteShortcutControllerName
269286
*/
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
// This file has been auto-generated by the Symfony Routing Component.
4+
5+
return [
6+
'_preview_error' => [
7+
['code', '_format'],
8+
['_controller' => 'error_controller::preview', '_format' => 'html'],
9+
['code' => '\\d+'],
10+
[
11+
['variable', '.', '[^/]++', '_format', true],
12+
['variable', '/', '\\d+', 'code', true],
13+
['text', '/_error']
14+
],
15+
[],
16+
[]
17+
],
18+
'_wdt' => [['token'], ['_controller' => 'web_profiler.controller.profiler::toolbarAction'], [], [['variable', '/', '[^/]++', 'token', true], ['text', '/_wdt']], [], []],
19+
'_profiler_home' => [[], ['_controller' => 'web_profiler.controller.profiler::homeAction'], [], [['text', '/_profiler/']], [], []],
20+
'_profiler_search' => [[], ['_controller' => 'web_profiler.controller.profiler::searchAction'], [], [['text', '/_profiler/search']], [], []],
21+
'_profiler_search_bar' => [[], ['_controller' => 'web_profiler.controller.profiler::searchBarAction'], [], [['text', '/_profiler/search_bar']], [], []],
22+
'_profiler_phpinfo' => [[], ['_controller' => 'web_profiler.controller.profiler::phpinfoAction'], [], [['text', '/_profiler/phpinfo']], [], []],
23+
'_profiler_search_results' => [['token'], ['_controller' => 'web_profiler.controller.profiler::searchResultsAction'], [], [['text', '/search/results'], ['variable', '/', '[^/]++', 'token', true], ['text', '/_profiler']], [], []],
24+
'_profiler_open_file' => [[], ['_controller' => 'web_profiler.controller.profiler::openAction'], [], [['text', '/_profiler/open']], [], []],
25+
'_profiler' => [
26+
['token'],
27+
['_controller' => 'web_profiler.controller.profiler::panelAction'],
28+
[],
29+
[
30+
['variable', '/', '[^/]++', 'token', true],
31+
['text', '/_profiler']
32+
],
33+
[],
34+
[]
35+
],
36+
'_profiler_router' => [['token'], ['_controller' => 'web_profiler.controller.router::panelAction'], [], [['text', '/router'], ['variable', '/', '[^/]++', 'token', true], ['text', '/_profiler']], [], []],
37+
'_profiler_exception' => [['token'], ['_controller' => 'web_profiler.controller.exception_panel::body'], [], [['text', '/exception'], ['variable', '/', '[^/]++', 'token', true], ['text', '/_profiler']], [], []],
38+
'_profiler_exception_css' => [['token'], ['_controller' => 'web_profiler.controller.exception_panel::stylesheet'], [], [['text', '/exception.css'], ['variable', '/', '[^/]++', 'token', true], ['text', '/_profiler']], [], []],
39+
];

0 commit comments

Comments
 (0)