@@ -70,24 +70,26 @@ final class RestrictImplicitDependencyUsage implements RestrictedClassNameUsageE
7070 ];
7171
7272 /**
73- * @var ComposerJson
73+ * @var array<string, ComposerJson>
7474 */
75- private array $ composerJson ;
75+ private array $ composerJsonCache = [] ;
7676
7777 /**
78- * @var InstalledJson
78+ * @var array<string, InstalledJson>
7979 */
80- private array $ installedJson ;
80+ private array $ installedJsonCache = [] ;
8181
8282 /**
83- * @var array<string, list<string>>
83+ * @var array<string, array<string, list<string> >>
8484 */
85- private array $ installedPackages ;
85+ private array $ installedPackagesCache = [] ;
8686
8787 /**
88- * @var list<string>
88+ * @var array<string, list<string> >
8989 */
90- private array $ allowedNamespaces ;
90+ private array $ allowedNamespacesCache = [];
91+
92+ private ?string $ currentModuleRoot = null ;
9193
9294 public function isRestrictedClassNameUsage (ClassReflection $ classReflection , Scope $ scope , ClassNameUsageLocation $ location ): ?RestrictedUsage
9395 {
@@ -99,6 +101,9 @@ public function isRestrictedClassNameUsage(ClassReflection $classReflection, Sco
99101 return null ;
100102 }
101103
104+ // Set the module root based on the file being analyzed
105+ $ this ->currentModuleRoot = $ this ->findModuleRoot ($ scope ->getFile ());
106+
102107 if ($ this ->isInAllowedNamespace ($ classReflection ->getName ())) {
103108 return null ;
104109 }
@@ -115,14 +120,46 @@ public function isRestrictedClassNameUsage(ClassReflection $classReflection, Sco
115120 );
116121 }
117122
123+ private function findModuleRoot (string $ file ): string
124+ {
125+ $ dir = dirname ($ file );
126+ while ($ dir !== '/ ' ) {
127+ if (file_exists ($ dir . '/composer.json ' )) {
128+ return $ dir ;
129+ }
130+ $ parent = dirname ($ dir );
131+ if ($ parent === $ dir ) {
132+ break ;
133+ }
134+ $ dir = $ parent ;
135+ }
136+
137+ // Fallback to basepath if no composer.json found
138+ return basepath () ?? getcwd ();
139+ }
140+
118141 public function getKey (): string
119142 {
120143 return 'restrict-implicit-dependency-usage ' ;
121144 }
122145
123146 public function getHash (): string
124147 {
125- return hash (serialize ($ this ->getComposerJson ()) . serialize ($ this ->getInstalledJson ()), Algorithm::Sha256);
148+ // Hash all cached modules
149+ $ hashes = [];
150+ foreach ($ this ->composerJsonCache as $ moduleRoot => $ composerJson ) {
151+ $ installedJson = $ this ->installedJsonCache [$ moduleRoot ] ?? [];
152+ $ hashes [] = hash (serialize ($ composerJson ) . serialize ($ installedJson ) . $ moduleRoot , Algorithm::Sha256);
153+ }
154+
155+ // If no modules cached yet, return a default hash
156+ if (empty ($ hashes )) {
157+ $ moduleRoot = basepath () ?? getcwd ();
158+ $ this ->currentModuleRoot = $ moduleRoot ;
159+ return hash (serialize ($ this ->getComposerJson ()) . serialize ($ this ->getInstalledJson ()) . $ moduleRoot , Algorithm::Sha256);
160+ }
161+
162+ return hash (implode ('' , $ hashes ), Algorithm::Sha256);
126163 }
127164
128165 public function isInGlobalNamespace (string $ class ): bool
@@ -153,7 +190,8 @@ public function getPackageNameForClass(string $class): ?string
153190 */
154191 private function getAllowedNamespaces (): array
155192 {
156- return $ this ->allowedNamespaces ??= [
193+ $ moduleRoot = $ this ->currentModuleRoot ?? basepath () ?? getcwd ();
194+ return $ this ->allowedNamespacesCache [$ moduleRoot ] ??= [
157195 ...$ this ->getOwnedNamespaces (),
158196 ...$ this ->getRequiredNamespaces (),
159197 ];
@@ -201,7 +239,8 @@ private function getRequiredPackages(): array
201239 */
202240 private function getInstalledPackagesWithNamespaces (): array
203241 {
204- return $ this ->installedPackages ??= [
242+ $ moduleRoot = $ this ->currentModuleRoot ?? basepath () ?? getcwd ();
243+ return $ this ->installedPackagesCache [$ moduleRoot ] ??= [
205244 ...from_entries (array_map (fn (array $ package ) => [$ package ['name ' ], keys ($ package ['autoload ' ]['psr-4 ' ] ?? [])], $ this ->getInstalledJson ()['packages ' ])),
206245 ...$ this ->getReplacedPackagesWithNamespaces (),
207246 ];
@@ -228,28 +267,30 @@ private function getReplacedPackages(): array
228267 */
229268 private function getInstalledJson (): array
230269 {
231- return $ this ->installedJson ??= Type \shape ([
270+ $ moduleRoot = $ this ->currentModuleRoot ?? basepath () ?? getcwd ();
271+ return $ this ->installedJsonCache [$ moduleRoot ] ??= Type \shape ([
232272 'packages ' => Type \vec (Type \shape ([
233273 'name ' => Type \string (),
234274 'autoload ' => Type \optional (Type \shape ([
235275 'psr-4 ' => Type \optional (Type \dict (Type \string (), Type \union (Type \string (), Type \vec (Type \string ())))),
236276 ], allow_unknown_fields: true )),
237277 'replace ' => Type \optional (Type \dict (Type \string (), Type \string ())),
238278 ], allow_unknown_fields: true )),
239- ], allow_unknown_fields: true )->assert (decode (read (basepath () . '/vendor/composer/installed.json ' )));
279+ ], allow_unknown_fields: true )->assert (decode (read ($ moduleRoot . '/vendor/composer/installed.json ' )));
240280 }
241281
242282 /**
243283 * @return ComposerJson
244284 */
245285 private function getComposerJson (): array
246286 {
247- return $ this ->composerJson ??= Type \shape ([
287+ $ moduleRoot = $ this ->currentModuleRoot ?? basepath () ?? getcwd ();
288+ return $ this ->composerJsonCache [$ moduleRoot ] ??= Type \shape ([
248289 'require ' => Type \optional (Type \dict (Type \string (), Type \string ())),
249290 'require-dev ' => Type \optional (Type \dict (Type \string (), Type \string ())),
250291 'autoload ' => Type \optional (Type \shape ([
251292 'psr-4 ' => Type \optional (Type \dict (Type \string (), Type \union (Type \string (), Type \vec (Type \string ())))),
252293 ], allow_unknown_fields: true )),
253- ], allow_unknown_fields: true )->assert (decode (read (' composer.json ' )));
294+ ], allow_unknown_fields: true )->assert (decode (read ($ moduleRoot . ' / composer.json ' )));
254295 }
255296}
0 commit comments