2
2
3
3
namespace PHPStan \Analyser \ResultCache ;
4
4
5
+ use Nette \DI \Definitions \Statement ;
5
6
use Nette \Neon \Neon ;
6
7
use PHPStan \Analyser \AnalyserResult ;
7
8
use PHPStan \Analyser \Error ;
11
12
use PHPStan \File \FileFinder ;
12
13
use PHPStan \File \FileReader ;
13
14
use PHPStan \File \FileWriter ;
15
+ use PHPStan \Reflection \ReflectionProvider ;
14
16
use function array_fill_keys ;
15
17
use function array_key_exists ;
16
18
17
19
class ResultCacheManager
18
20
{
19
21
20
- private const CACHE_VERSION = 'v8-executed-hash ' ;
22
+ private const CACHE_VERSION = 'v9-project-extensions ' ;
21
23
22
24
private ExportedNodeFetcher $ exportedNodeFetcher ;
23
25
24
26
private FileFinder $ scanFileFinder ;
25
27
28
+ private ReflectionProvider $ reflectionProvider ;
29
+
26
30
private string $ cacheFilePath ;
27
31
28
32
private string $ tempResultCachePath ;
@@ -55,9 +59,13 @@ class ResultCacheManager
55
59
/** @var array<string, string> */
56
60
private array $ fileReplacements = [];
57
61
62
+ /** @var array<string, true> */
63
+ private array $ alreadyProcessed = [];
64
+
58
65
/**
59
66
* @param ExportedNodeFetcher $exportedNodeFetcher
60
67
* @param FileFinder $scanFileFinder
68
+ * @param ReflectionProvider $reflectionProvider
61
69
* @param string $cacheFilePath
62
70
* @param string $tempResultCachePath
63
71
* @param string[] $analysedPaths
@@ -73,6 +81,7 @@ class ResultCacheManager
73
81
public function __construct (
74
82
ExportedNodeFetcher $ exportedNodeFetcher ,
75
83
FileFinder $ scanFileFinder ,
84
+ ReflectionProvider $ reflectionProvider ,
76
85
string $ cacheFilePath ,
77
86
string $ tempResultCachePath ,
78
87
array $ analysedPaths ,
@@ -88,6 +97,7 @@ public function __construct(
88
97
{
89
98
$ this ->exportedNodeFetcher = $ exportedNodeFetcher ;
90
99
$ this ->scanFileFinder = $ scanFileFinder ;
100
+ $ this ->reflectionProvider = $ reflectionProvider ;
91
101
$ this ->cacheFilePath = $ cacheFilePath ;
92
102
$ this ->tempResultCachePath = $ tempResultCachePath ;
93
103
$ this ->analysedPaths = $ analysedPaths ;
@@ -159,7 +169,7 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
159
169
}
160
170
161
171
$ meta = $ this ->getMeta ($ allAnalysedFiles , $ projectConfigArray );
162
- if ($ data ['meta ' ] !== $ meta ) {
172
+ if ($ this -> isMetaDifferent ( $ data ['meta ' ], $ meta) ) {
163
173
if ($ output ->isDebug ()) {
164
174
$ output ->writeLineFormatted ('Result cache not used because the metadata do not match. ' );
165
175
}
@@ -174,6 +184,25 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
174
184
return new ResultCache ($ allAnalysedFiles , true , time (), $ meta , [], [], []);
175
185
}
176
186
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
+
177
206
$ invertedDependencies = $ data ['dependencies ' ];
178
207
$ deletedFiles = array_fill_keys (array_keys ($ invertedDependencies ), true );
179
208
$ filesToAnalyse = [];
@@ -254,6 +283,21 @@ public function restore(array $allAnalysedFiles, bool $debug, bool $onlyFiles, ?
254
283
return new ResultCache (array_unique ($ filesToAnalyse ), false , $ data ['lastFullAnalysisTime ' ], $ meta , $ filteredErrors , $ invertedDependenciesToReturn , $ filteredExportedNodes );
255
284
}
256
285
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
+
257
301
/**
258
302
* @param string $analysedFile
259
303
* @param array<int, ExportedNode> $cachedFileExportedNodes
@@ -520,6 +564,7 @@ private function save(
520
564
return [
521
565
'lastFullAnalysisTime' => %s,
522
566
'meta' => %s,
567
+ 'projectExtensionFiles' => %s,
523
568
'errorsCallback' => static function (): array { return %s; },
524
569
'dependencies' => %s,
525
570
'exportedNodesCallback' => static function (): array { return %s; },
@@ -533,19 +578,123 @@ private function save(
533
578
$ file = $ this ->tempResultCachePath . '/ ' . $ resultCacheName . '.php ' ;
534
579
}
535
580
581
+ $ projectConfigArray = $ meta ['projectConfig ' ];
582
+ if ($ projectConfigArray !== null ) {
583
+ $ meta ['projectConfig ' ] = Neon::encode ($ projectConfigArray );
584
+ }
585
+
536
586
FileWriter::write (
537
587
$ file ,
538
588
sprintf (
539
589
$ template ,
540
590
var_export ($ lastFullAnalysisTime , true ),
541
591
var_export ($ meta , true ),
592
+ var_export ($ this ->getProjectExtensionFiles ($ projectConfigArray , $ dependencies ), true ),
542
593
var_export ($ errors , true ),
543
594
var_export ($ invertedDependencies , true ),
544
595
var_export ($ exportedNodes , true )
545
596
)
546
597
);
547
598
}
548
599
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
+
549
698
/**
550
699
* @param string[] $allAnalysedFiles
551
700
* @param mixed[]|null $projectConfigArray
@@ -567,8 +716,6 @@ private function getMeta(array $allAnalysedFiles, ?array $projectConfigArray): a
567
716
unset($ projectConfigArray ['parameters ' ]['reportUnmatchedIgnoredErrors ' ]);
568
717
unset($ projectConfigArray ['parameters ' ]['memoryLimitFile ' ]);
569
718
unset($ projectConfigArray ['parametersSchema ' ]);
570
-
571
- $ projectConfigArray = Neon::encode ($ projectConfigArray );
572
719
}
573
720
574
721
return [
0 commit comments