22
33import com .intellij .openapi .project .Project ;
44import com .intellij .openapi .util .Key ;
5+ import com .intellij .openapi .vfs .VirtualFileManager ;
56import com .intellij .psi .util .CachedValue ;
67import com .intellij .psi .util .CachedValueProvider ;
78import com .intellij .psi .util .CachedValuesManager ;
8- import com .intellij . psi .util . PsiModificationTracker ;
9+ import com .jetbrains . php . lang . psi .stubs . indexes . PhpClassFqnIndex ;
910import com .jetbrains .php .lang .psi .elements .Method ;
1011import com .jetbrains .php .lang .psi .elements .PhpClass ;
1112import fr .adrienbrault .idea .symfony2plugin .routing .Route ;
1213import fr .adrienbrault .idea .symfony2plugin .routing .RouteHelper ;
1314import fr .adrienbrault .idea .symfony2plugin .stubs .ContainerCollectionResolver ;
15+ import fr .adrienbrault .idea .symfony2plugin .stubs .cache .FileIndexCaches ;
16+ import fr .adrienbrault .idea .symfony2plugin .stubs .indexes .PhpAttributeIndex ;
17+ import fr .adrienbrault .idea .symfony2plugin .stubs .indexes .RoutesStubIndex ;
18+ import fr .adrienbrault .idea .symfony2plugin .stubs .indexes .ServicesDefinitionStubIndex ;
19+ import fr .adrienbrault .idea .symfony2plugin .util .SymfonyVarDirectoryWatcher ;
20+ import fr .adrienbrault .idea .symfony2plugin .util .SymfonyVarDirectoryWatcherKt ;
1421import fr .adrienbrault .idea .symfony2plugin .util .dict .ServiceUtil ;
1522import org .apache .commons .lang3 .StringUtils ;
1623import org .jetbrains .annotations .NotNull ;
2229 */
2330public class ServiceRouteContainer {
2431
25- private static final Key <CachedValue <ServiceRouteContainer >> SERVICE_ROUTE_CONTAINER_CACHE = new Key <>("SYMFONY_SERVICE_ROUTE_CONTAINER_CACHE" );
26-
27- private final Collection <Route > routes ;
28- // Lazily built index: method name -> matching route entries. Null until first use.
29- private Map <String , List <RouteEntry >> routesByMethodName ;
30- private ContainerCollectionResolver .LazyServiceCollector lazyServiceCollector ;
31-
32- private final Map <String , PhpClass > serviceCache = new HashMap <>();
32+ private static final Key <CachedValue <ServiceRouteData >> SERVICE_ROUTE_DATA_CACHE = new Key <>("SYMFONY_SERVICE_ROUTE_DATA_CACHE" );
3333
3434 // Holds the pre-split controller parts alongside the Route to avoid re-splitting on every lookup.
3535 private record RouteEntry (String serviceId , String methodName , Route route ) {}
3636
37- private ServiceRouteContainer (@ NotNull Collection <Route > routes ) {
38- this .routes = routes ;
39- }
37+ private record ServiceRouteData (@ NotNull Map <String , List <RouteEntry >> routesByMethodName , @ NotNull Set <String > serviceNames ) {}
4038
4139 /**
42- * Builds the method-name index on the first call and caches it for subsequent calls.
43- * Splitting is done here once with indexOf instead of split() to avoid repeated array allocations.
40+ * Returns service ids referenced by service-style route controllers.
4441 */
45- private Map <String , List <RouteEntry >> getRoutesByMethodName () {
46- if (routesByMethodName != null ) {
47- return routesByMethodName ;
48- }
49-
50- Map <String , List <RouteEntry >> index = new HashMap <>();
51- for (Route route : routes ) {
52- String controller = route .getController ();
53- if (controller == null ) continue ;
54- // controller format: "service_id:methodName" or "service_id::methodName"
55- String normalizedController = controller .replace ("::" , ":" );
56- int colon = normalizedController .indexOf (':' );
57- if (colon > 0 && colon < normalizedController .length () - 1 ) {
58- String serviceId = normalizedController .substring (0 , colon );
59- String methodName = normalizedController .substring (colon + 1 );
60- index .computeIfAbsent (methodName , k -> new ArrayList <>()).add (new RouteEntry (serviceId , methodName , route ));
61- }
62- }
63-
64- return routesByMethodName = index ;
65- }
66-
67- public Set <String > getServiceNames () {
68- Set <String > services = new HashSet <>();
69- for (List <RouteEntry > entries : getRoutesByMethodName ().values ()) {
70- for (RouteEntry entry : entries ) {
71- services .add (entry .serviceId ());
72- }
73- }
74- return services ;
42+ @ NotNull
43+ public static Set <String > getServiceNames (@ NotNull Project project ) {
44+ return getServiceRouteData (project ).serviceNames ();
7545 }
7646
7747 @ NotNull
78- public Collection <Route > getMethodMatches (@ NotNull Method method ) {
48+ public static Collection <Route > getMethodMatchesForRouteController (@ NotNull Method method ) {
7949 PhpClass originClass = method .getContainingClass ();
8050 if (originClass == null ) {
8151 return Collections .emptyList ();
8252 }
8353
8454 // Fast path: no routes registered for this method name at all
85- List <RouteEntry > candidates = getRoutesByMethodName ().get (method .getName ());
55+ List <RouteEntry > candidates = getServiceRouteData ( method . getProject ()). routesByMethodName ().get (method .getName ());
8656 if (candidates == null ) {
8757 return Collections .emptyList ();
8858 }
8959
9060 String classFqn = StringUtils .stripStart (originClass .getFQN (), "\\ " );
9161
9262 Collection <Route > routes = new ArrayList <>();
63+ Map <String , PhpClass > serviceCache = new HashMap <>();
64+ ContainerCollectionResolver .LazyServiceCollector lazyServiceCollector = new ContainerCollectionResolver .LazyServiceCollector (method .getProject ());
9365 for (RouteEntry entry : candidates ) {
9466 // cache PhpClass resolve
9567 if (!serviceCache .containsKey (entry .serviceId ())) {
96- serviceCache .put (entry .serviceId (), ServiceUtil .getResolvedClassDefinition (method .getProject (), entry .serviceId (), getLazyServiceCollector ( method . getProject ()) ));
68+ serviceCache .put (entry .serviceId (), ServiceUtil .getResolvedClassDefinition (method .getProject (), entry .serviceId (), lazyServiceCollector ));
9769 }
9870
9971 PhpClass phpClass = serviceCache .get (entry .serviceId ());
@@ -107,33 +79,39 @@ public Collection<Route> getMethodMatches(@NotNull Method method) {
10779 return routes ;
10880 }
10981
110- private ContainerCollectionResolver .LazyServiceCollector getLazyServiceCollector (Project project ) {
111- return this .lazyServiceCollector == null ? this .lazyServiceCollector = new ContainerCollectionResolver .LazyServiceCollector (project ) : this .lazyServiceCollector ;
112- }
113-
11482 @ NotNull
115- public static ServiceRouteContainer build (@ NotNull Project project ) {
83+ private static ServiceRouteData getServiceRouteData (@ NotNull Project project ) {
11684 return CachedValuesManager .getManager (project ).getCachedValue (
11785 project ,
118- SERVICE_ROUTE_CONTAINER_CACHE ,
119- () -> {
120- ServiceRouteContainer container = buildUncached (project , RouteHelper .getAllRoutes (project ));
121- return CachedValueProvider .Result .create (
122- container ,
123- PsiModificationTracker .MODIFICATION_COUNT
124- );
125- },
86+ SERVICE_ROUTE_DATA_CACHE ,
87+ () -> CachedValueProvider .Result .create (
88+ buildUncached (project , RouteHelper .getAllRoutes (project )),
89+ getCacheDependencies (project )
90+ ),
12691 false
12792 );
12893 }
12994
95+ private static Object @ NotNull [] getCacheDependencies (@ NotNull Project project ) {
96+ return new Object [] {
97+ FileIndexCaches .getModificationTrackerForIndexId (project , RoutesStubIndex .KEY ),
98+ SymfonyVarDirectoryWatcherKt .getSymfonyVarDirectoryWatcher (project ).getModificationTracker (SymfonyVarDirectoryWatcher .Scope .ROUTES ),
99+ FileIndexCaches .getModificationTrackerForIndexId (project , ServicesDefinitionStubIndex .KEY ),
100+ FileIndexCaches .getModificationTrackerForIndexId (project , PhpAttributeIndex .KEY ),
101+ FileIndexCaches .getModificationTrackerForIndexId (project , PhpClassFqnIndex .KEY ),
102+ SymfonyVarDirectoryWatcherKt .getSymfonyVarDirectoryWatcher (project ).getModificationTracker (SymfonyVarDirectoryWatcher .Scope .CONTAINER ),
103+ VirtualFileManager .VFS_STRUCTURE_MODIFICATIONS
104+ };
105+ }
106+
130107 /**
131- * Build container which stores all service routes
108+ * Build route lookup data for service-style route controllers.
132109 *
133110 * @param routes Unfiltered routes
134111 */
135- private static ServiceRouteContainer buildUncached (@ NotNull Project project , @ NotNull Map <String , Route > routes ) {
136- Collection <Route > serviceRoutes = new ArrayList <>();
112+ private static ServiceRouteData buildUncached (@ NotNull Project project , @ NotNull Map <String , Route > routes ) {
113+ Map <String , List <RouteEntry >> routesByMethodName = new HashMap <>();
114+ Set <String > serviceNames = new HashSet <>();
137115
138116 ContainerCollectionResolver .LazyServiceCollector lazyServiceCollector = null ;
139117
@@ -150,11 +128,21 @@ private static ServiceRouteContainer buildUncached(@NotNull Project project, @No
150128 }
151129
152130 if (split .length > 1 && ContainerCollectionResolver .hasServiceName (lazyServiceCollector , split [0 ])) {
153- serviceRoutes .add (route );
131+ String serviceId = split [0 ];
132+ String methodName = split [1 ];
133+ routesByMethodName .computeIfAbsent (methodName , key -> new ArrayList <>()).add (new RouteEntry (serviceId , methodName , route ));
134+ serviceNames .add (serviceId );
154135 }
155136 }
156137
157- return new ServiceRouteContainer (serviceRoutes );
158- }
138+ Map <String , List <RouteEntry >> unmodifiableRoutesByMethodName = new HashMap <>();
139+ for (Map .Entry <String , List <RouteEntry >> entry : routesByMethodName .entrySet ()) {
140+ unmodifiableRoutesByMethodName .put (entry .getKey (), Collections .unmodifiableList (entry .getValue ()));
141+ }
159142
143+ return new ServiceRouteData (
144+ Collections .unmodifiableMap (unmodifiableRoutesByMethodName ),
145+ Collections .unmodifiableSet (serviceNames )
146+ );
147+ }
160148}
0 commit comments