22
33namespace PHPStan \Analyser \ResultCache ;
44
5+ use Nette \DI \Definitions \Statement ;
56use Nette \Neon \Neon ;
67use PHPStan \Analyser \AnalyserResult ;
78use PHPStan \Analyser \Error ;
1112use PHPStan \File \FileFinder ;
1213use PHPStan \File \FileReader ;
1314use PHPStan \File \FileWriter ;
15+ use PHPStan \Reflection \ReflectionProvider ;
1416use function array_fill_keys ;
1517use function array_key_exists ;
1618
1719class ResultCacheManager
1820{
1921
20- private const CACHE_VERSION = 'v8-executed-hash ' ;
22+ private const CACHE_VERSION = 'v9-project-extensions ' ;
2123
2224 private ExportedNodeFetcher $ exportedNodeFetcher ;
2325
2426 private FileFinder $ scanFileFinder ;
2527
28+ private ReflectionProvider $ reflectionProvider ;
29+
2630 private string $ cacheFilePath ;
2731
2832 private string $ tempResultCachePath ;
@@ -55,9 +59,13 @@ class ResultCacheManager
5559 /** @var array<string, string> */
5660 private array $ fileReplacements = [];
5761
62+ /** @var array<string, true> */
63+ private array $ alreadyProcessed = [];
64+
5865 /**
5966 * @param ExportedNodeFetcher $exportedNodeFetcher
6067 * @param FileFinder $scanFileFinder
68+ * @param ReflectionProvider $reflectionProvider
6169 * @param string $cacheFilePath
6270 * @param string $tempResultCachePath
6371 * @param string[] $analysedPaths
@@ -73,6 +81,7 @@ class ResultCacheManager
7381 public function __construct (
7482 ExportedNodeFetcher $ exportedNodeFetcher ,
7583 FileFinder $ scanFileFinder ,
84+ ReflectionProvider $ reflectionProvider ,
7685 string $ cacheFilePath ,
7786 string $ tempResultCachePath ,
7887 array $ analysedPaths ,
@@ -88,6 +97,7 @@ public function __construct(
8897 {
8998 $ this ->exportedNodeFetcher = $ exportedNodeFetcher ;
9099 $ this ->scanFileFinder = $ scanFileFinder ;
100+ $ this ->reflectionProvider = $ reflectionProvider ;
91101 $ this ->cacheFilePath = $ cacheFilePath ;
92102 $ this ->tempResultCachePath = $ tempResultCachePath ;
93103 $ this ->analysedPaths = $ analysedPaths ;
@@ -159,7 +169,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
159169 }
160170
161171 $ meta = $ this ->getMeta ($ allAnalysedFiles , $ projectConfigArray );
162- if ($ data ['meta ' ] !== $ meta ) {
172+ if ($ this -> isMetaDifferent ( $ data ['meta ' ], $ meta) ) {
163173 if ($ output ->isDebug ()) {
164174 $ output ->writeLineFormatted ('Result cache not used because the metadata do not match. ' );
165175 }
@@ -174,6 +184,25 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
174184 return new ResultCache ($ allAnalysedFiles , true , time (), $ meta , [], [], []);
175185 }
176186
187+ foreach ($ data ['projectExtensionFiles ' ] as $ extensionFile => $ fileHash ) {
188+ if (!is_file ($ extensionFile )) {
189+ if ($ output ->isDebug ()) {
190+ $ output ->writeLineFormatted (sprintf ('Result cache not used because extension file %s was not found. ' , $ extensionFile ));
191+ }
192+ return new ResultCache ($ allAnalysedFiles , true , time (), $ meta , [], [], []);
193+ }
194+
195+ if ($ this ->getFileHash ($ extensionFile ) === $ fileHash ) {
196+ continue ;
197+ }
198+
199+ if ($ output ->isDebug ()) {
200+ $ output ->writeLineFormatted (sprintf ('Result cache not used because extension file %s hash does not match. ' , $ extensionFile ));
201+ }
202+
203+ return new ResultCache ($ allAnalysedFiles , true , time (), $ meta , [], [], []);
204+ }
205+
177206 $ invertedDependencies = $ data ['dependencies ' ];
178207 $ deletedFiles = array_fill_keys (array_keys ($ invertedDependencies ), true );
179208 $ filesToAnalyse = [];
@@ -254,6 +283,21 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
254283 return new ResultCache (array_unique ($ filesToAnalyse ), false , $ data ['lastFullAnalysisTime ' ], $ meta , $ filteredErrors , $ invertedDependenciesToReturn , $ filteredExportedNodes );
255284 }
256285
286+ /**
287+ * @param mixed[] $cachedMeta
288+ * @param mixed[] $currentMeta
289+ * @return bool
290+ */
291+ private function isMetaDifferent (array $ cachedMeta , array $ currentMeta ): bool
292+ {
293+ $ projectConfig = $ currentMeta ['projectConfig ' ];
294+ if ($ projectConfig !== null ) {
295+ $ currentMeta ['projectConfig ' ] = Neon::encode ($ currentMeta ['projectConfig ' ]);
296+ }
297+
298+ return $ cachedMeta !== $ currentMeta ;
299+ }
300+
257301 /**
258302 * @param string $analysedFile
259303 * @param array<int, ExportedNode> $cachedFileExportedNodes
@@ -520,6 +564,7 @@ private function save(
520564return [
521565 'lastFullAnalysisTime' => %s,
522566 'meta' => %s,
567+ 'projectExtensionFiles' => %s,
523568 'errorsCallback' => static function (): array { return %s; },
524569 'dependencies' => %s,
525570 'exportedNodesCallback' => static function (): array { return %s; },
@@ -533,19 +578,123 @@ private function save(
533578 $ file = $ this ->tempResultCachePath . '/ ' . $ resultCacheName . '.php ' ;
534579 }
535580
581+ $ projectConfigArray = $ meta ['projectConfig ' ];
582+ if ($ projectConfigArray !== null ) {
583+ $ meta ['projectConfig ' ] = Neon::encode ($ projectConfigArray );
584+ }
585+
536586 FileWriter::write (
537587 $ file ,
538588 sprintf (
539589 $ template ,
540590 var_export ($ lastFullAnalysisTime , true ),
541591 var_export ($ meta , true ),
592+ var_export ($ this ->getProjectExtensionFiles ($ projectConfigArray , $ dependencies ), true ),
542593 var_export ($ errors , true ),
543594 var_export ($ invertedDependencies , true ),
544595 var_export ($ exportedNodes , true )
545596 )
546597 );
547598 }
548599
600+ /**
601+ * @param mixed[]|null $projectConfig
602+ * @param array<string, mixed> $dependencies
603+ * @return array<string, string>
604+ */
605+ private function getProjectExtensionFiles (?array $ projectConfig , array $ dependencies ): array
606+ {
607+ $ this ->alreadyProcessed = [];
608+ $ projectExtensionFiles = [];
609+ if ($ projectConfig !== null ) {
610+ $ services = $ projectConfig ['services ' ] ?? [];
611+ foreach ($ services as $ service ) {
612+ $ classes = $ this ->getClassesFromConfigDefinition ($ service );
613+ if (is_array ($ service )) {
614+ foreach (['class ' , 'factory ' , 'implement ' ] as $ key ) {
615+ if (!isset ($ service [$ key ])) {
616+ continue ;
617+ }
618+
619+ $ classes = array_merge ($ classes , $ this ->getClassesFromConfigDefinition ($ service [$ key ]));
620+ }
621+ }
622+
623+ foreach (array_unique ($ classes ) as $ class ) {
624+ if (!$ this ->reflectionProvider ->hasClass ($ class )) {
625+ continue ;
626+ }
627+
628+ $ classReflection = $ this ->reflectionProvider ->getClass ($ class );
629+ $ fileName = $ classReflection ->getFileName ();
630+ if ($ fileName === false ) {
631+ continue ;
632+ }
633+
634+ $ allServiceFiles = $ this ->getAllDependencies ($ fileName , $ dependencies );
635+ foreach ($ allServiceFiles as $ serviceFile ) {
636+ if (array_key_exists ($ serviceFile , $ projectExtensionFiles )) {
637+ continue ;
638+ }
639+
640+ $ projectExtensionFiles [$ serviceFile ] = $ this ->getFileHash ($ serviceFile );
641+ }
642+ }
643+ }
644+ }
645+
646+ return $ projectExtensionFiles ;
647+ }
648+
649+ /**
650+ * @param mixed $definition
651+ * @return string[]
652+ */
653+ private function getClassesFromConfigDefinition ($ definition ): array
654+ {
655+ if (is_string ($ definition )) {
656+ return [$ definition ];
657+ }
658+
659+ if ($ definition instanceof Statement) {
660+ $ entity = $ definition ->entity ;
661+ if (is_string ($ entity )) {
662+ return [$ entity ];
663+ } elseif (is_array ($ entity ) && isset ($ entity [0 ]) && is_string ($ entity [0 ])) {
664+ return [$ entity [0 ]];
665+ }
666+ }
667+
668+ return [];
669+ }
670+
671+ /**
672+ * @param string $fileName
673+ * @param array<string, array<int, string>> $dependencies
674+ * @return array<int, string>
675+ */
676+ private function getAllDependencies (string $ fileName , array $ dependencies ): array
677+ {
678+ if (!array_key_exists ($ fileName , $ dependencies )) {
679+ return [];
680+ }
681+
682+ if (array_key_exists ($ fileName , $ this ->alreadyProcessed )) {
683+ return [];
684+ }
685+
686+ $ this ->alreadyProcessed [$ fileName ] = true ;
687+
688+ $ files = [$ fileName ];
689+ foreach ($ dependencies [$ fileName ] as $ fileDep ) {
690+ foreach ($ this ->getAllDependencies ($ fileDep , $ dependencies ) as $ fileDep2 ) {
691+ $ files [] = $ fileDep2 ;
692+ }
693+ }
694+
695+ return $ files ;
696+ }
697+
549698 /**
550699 * @param string[] $allAnalysedFiles
551700 * @param mixed[]|null $projectConfigArray
@@ -567,8 +716,6 @@ private function getMeta(array $allAnalysedFiles, ?array $projectConfigArray): a
567716 unset($ projectConfigArray ['parameters ' ]['reportUnmatchedIgnoredErrors ' ]);
568717 unset($ projectConfigArray ['parameters ' ]['memoryLimitFile ' ]);
569718 unset($ projectConfigArray ['parametersSchema ' ]);
570-
571- $ projectConfigArray = Neon::encode ($ projectConfigArray );
572719 }
573720
574721 return [
0 commit comments