88use PHPStan \Analyser \NameScope ;
99use PHPStan \BetterReflection \Util \GetLastDocComment ;
1010use PHPStan \Broker \AnonymousClassNameHelper ;
11+ use PHPStan \Cache \Cache ;
1112use PHPStan \DependencyInjection \AutowiredParameter ;
1213use PHPStan \DependencyInjection \AutowiredService ;
1314use PHPStan \File \FileHelper ;
3738use function array_reverse ;
3839use function array_slice ;
3940use function count ;
41+ use function hash_file ;
4042use function in_array ;
4143use function is_array ;
4244use function is_file ;
@@ -54,7 +56,7 @@ final class FileTypeMapper
5456 private const SKIP_NODE = 1 ;
5557 private const POP_TYPE_MAP_STACK = 2 ;
5658
57- /** @var array<string, array{array<string, PhpDocNode>, array<string, IntermediaryNameScope>}> */
59+ /** @var array<string, array{array<string, IntermediaryNameScope>}> */
5860 private array $ memoryCache = [];
5961
6062 private int $ memoryCacheCount = 0 ;
@@ -75,6 +77,7 @@ public function __construct(
7577 private PhpDocNodeResolver $ phpDocNodeResolver ,
7678 private AnonymousClassNameHelper $ anonymousClassNameHelper ,
7779 private FileHelper $ fileHelper ,
80+ private Cache $ cache ,
7881 )
7982 {
8083 }
@@ -197,7 +200,7 @@ public function getNameScope(
197200 throw new NameScopeAlreadyBeingCreatedException ();
198201 }
199202
200- [, $ nameScopeMap ] = $ this ->getNameScopeMap ($ fileName );
203+ [$ nameScopeMap ] = $ this ->getNameScopeMap ($ fileName );
201204 if (!isset ($ nameScopeMap [$ nameScopeKey ])) {
202205 throw new NameScopeAlreadyBeingCreatedException ();
203206 }
@@ -317,12 +320,25 @@ public function getNameScope(
317320 }
318321
319322 /**
320- * @return array{array<string, PhpDocNode>, array<string, IntermediaryNameScope>}
323+ * @return array{array<string, IntermediaryNameScope>}
321324 */
322325 private function getNameScopeMap (string $ fileName ): array
323326 {
324327 if (!isset ($ this ->memoryCache [$ fileName ])) {
325- [$ phpDocNodeMap , $ nameScopeMap ] = $ this ->createPhpDocNodeMap ($ fileName , null , null , [], $ fileName );
328+ $ cacheKey = sprintf ('ftm-%s ' , $ fileName );
329+ $ variableCacheKey = 'v2 ' ;
330+ $ cached = $ this ->loadCachedPhpDocNodeMap ($ cacheKey , $ variableCacheKey );
331+ if ($ cached === null ) {
332+ [$ nameScopeMap , $ files ] = $ this ->createPhpDocNodeMap ($ fileName , null , null , [], $ fileName );
333+ $ filesWithHashes = [];
334+ foreach ($ files as $ file ) {
335+ $ newHash = hash_file ('sha256 ' , $ file );
336+ $ filesWithHashes [$ file ] = $ newHash ;
337+ }
338+ $ this ->cache ->save ($ cacheKey , $ variableCacheKey , [$ nameScopeMap , $ filesWithHashes ]);
339+ } else {
340+ [$ nameScopeMap , $ files ] = $ cached ;
341+ }
326342 if ($ this ->memoryCacheCount >= 2048 ) {
327343 $ this ->memoryCache = array_slice (
328344 $ this ->memoryCache ,
@@ -332,22 +348,53 @@ private function getNameScopeMap(string $fileName): array
332348 $ this ->memoryCacheCount --;
333349 }
334350
335- $ this ->memoryCache [$ fileName ] = [$ phpDocNodeMap , $ nameScopeMap ];
351+ $ this ->memoryCache [$ fileName ] = [$ nameScopeMap , $ files ];
336352 $ this ->memoryCacheCount ++;
337353 }
338354
339355 return $ this ->memoryCache [$ fileName ];
340356 }
341357
358+ /**
359+ * @param non-empty-string $cacheKey
360+ * @return array{array<string, IntermediaryNameScope>, list<string>}|null
361+ */
362+ private function loadCachedPhpDocNodeMap (string $ cacheKey , string $ variableCacheKey ): ?array
363+ {
364+ $ cached = $ this ->cache ->load ($ cacheKey , $ variableCacheKey );
365+ if ($ cached !== null ) {
366+ /**
367+ * @var array<string, string> $filesWithHashes
368+ */
369+ [$ nameScopeMap , $ filesWithHashes ] = $ cached ;
370+ $ useCache = true ;
371+ foreach ($ filesWithHashes as $ file => $ hash ) {
372+ if (!is_file ($ file )) {
373+ $ useCache = false ;
374+ break ;
375+ }
376+ $ newHash = hash_file ('sha256 ' , $ file );
377+ if ($ newHash === $ hash ) {
378+ continue ;
379+ }
380+ $ useCache = false ;
381+ break ;
382+ }
383+
384+ if ($ useCache ) {
385+ return [$ nameScopeMap , array_keys ($ filesWithHashes )];
386+ }
387+ }
388+
389+ return null ;
390+ }
391+
342392 /**
343393 * @param array<string, string> $traitMethodAliases
344- * @return array{array<string, PhpDocNode >, array <string, IntermediaryNameScope >}
394+ * @return array{array<string, IntermediaryNameScope >, list <string>}
345395 */
346396 private function createPhpDocNodeMap (string $ fileName , ?string $ lookForTrait , ?string $ traitUseClass , array $ traitMethodAliases , string $ originalClassFileName ): array
347397 {
348- /** @var array<string, PhpDocNode> $phpDocNodeMap */
349- $ phpDocNodeMap = [];
350-
351398 /** @var array<string, IntermediaryNameScope> $nameScopeMap */
352399 $ nameScopeMap = [];
353400
@@ -367,13 +414,15 @@ private function createPhpDocNodeMap(string $fileName, ?string $lookForTrait, ?s
367414
368415 $ traitFound = false ;
369416
417+ $ files = [$ fileName ];
418+
370419 /** @var array<string|null> $functionStack */
371420 $ functionStack = [];
372421 $ uses = [];
373422 $ constUses = [];
374423 $ this ->processNodes (
375424 $ this ->phpParser ->parseFile ($ fileName ),
376- function (Node $ node ) use ($ fileName , $ lookForTrait , &$ traitFound , $ traitMethodAliases , $ originalClassFileName , &$ phpDocNodeMap , & $ nameScopeMap , &$ typeMapStack , &$ typeAliasStack , &$ classStack , &$ namespace , &$ functionStack , &$ uses , &$ constUses ): ?int {
425+ function (Node $ node ) use ($ fileName , $ lookForTrait , &$ traitFound , $ traitMethodAliases , $ originalClassFileName , &$ nameScopeMap , &$ typeMapStack , &$ typeAliasStack , &$ classStack , &$ namespace , &$ functionStack , &$ uses , &$ constUses, & $ files ): ?int {
377426 if ($ node instanceof Node \Stmt \ClassLike) {
378427 if ($ traitFound && $ fileName === $ originalClassFileName ) {
379428 return self ::SKIP_NODE ;
@@ -434,13 +483,7 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
434483 ) {
435484 $ docComment = GetLastDocComment::forNode ($ node );
436485 if ($ docComment !== null ) {
437- $ phpDocKey = $ this ->getPhpDocKey ($ nameScopeKey , $ docComment );
438486 $ phpDocNode = $ this ->phpDocStringResolver ->resolve ($ docComment );
439- $ phpDocNodeMap [$ phpDocKey ] = $ phpDocNode ;
440- $ plainDocComment = $ node ->getDocComment ();
441- if ($ plainDocComment !== null && $ plainDocComment ->getText () !== $ docComment ) {
442- $ phpDocNodeMap [$ this ->getPhpDocKey ($ nameScopeKey , $ plainDocComment ->getText ())] = $ phpDocNode ;
443- }
444487 }
445488 }
446489
@@ -576,16 +619,15 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
576619 throw new ShouldNotHappenException ();
577620 }
578621
579- [$ traitPhpDocNodeMap , $ traitNameScopeMap ] = $ this ->createPhpDocNodeMap (
622+ [$ traitNameScopeMap , $ traitFiles ] = $ this ->createPhpDocNodeMap (
580623 $ traitReflection ->getFileName (),
581624 $ traitName ,
582625 $ className ,
583626 $ traitMethodAliases [$ traitName ] ?? [],
584627 $ originalClassFileName ,
585628 );
586- $ phpDocNodeMap = array_merge ($ phpDocNodeMap , $ traitPhpDocNodeMap );
587629 $ nameScopeMap = array_merge ($ nameScopeMap , array_map (static fn ($ originalNameScope ) => $ originalNameScope ->getTraitData () === null ? $ originalNameScope ->withTraitData ($ fileName , $ className , $ traitName , $ lookForTrait , $ docComment ) : $ originalNameScope , $ traitNameScopeMap ));
588-
630+ $ files = array_merge ( $ files , $ traitFiles );
589631 }
590632 }
591633
@@ -644,7 +686,7 @@ static function (Node $node, $callbackResult) use (&$namespace, &$functionStack,
644686 throw new ShouldNotHappenException ();
645687 }
646688
647- return [$ phpDocNodeMap , $ nameScopeMap ];
689+ return [$ nameScopeMap , $ files ];
648690 }
649691
650692 /**
0 commit comments