From e3b29a54dee87b66617e0cf98d87cf2a2353ccf8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 14 Feb 2026 15:00:25 +0100 Subject: [PATCH 1/6] Cache PHPStorm stubs --- .../BetterReflectionSourceLocatorFactory.php | 14 ++- .../PhpVersionBlacklistSourceLocator.php | 5 +- .../CachedPhpStormStubsSourceStubber.php | 98 +++++++++++++++++++ 3 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 src/Reflection/BetterReflection/SourceStubber/CachedPhpStormStubsSourceStubber.php diff --git a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php index ddffaffc89..1cee568775 100644 --- a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php @@ -13,6 +13,7 @@ use PHPStan\BetterReflection\SourceLocator\Type\MemoizingSourceLocator; use PHPStan\BetterReflection\SourceLocator\Type\PhpInternalSourceLocator; use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator; +use PHPStan\Cache\Cache; use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\PhpVersion; @@ -28,6 +29,7 @@ use PHPStan\Reflection\BetterReflection\SourceLocator\RewriteClassAliasSourceLocator; use PHPStan\Reflection\BetterReflection\SourceLocator\SkipClassAliasSourceLocator; use PHPStan\Reflection\BetterReflection\SourceLocator\SkipPolyfillSourceLocator; +use PHPStan\Reflection\BetterReflection\SourceStubber\CachedPhpStormStubsSourceStubber; use function array_merge; use function array_unique; use function count; @@ -74,6 +76,7 @@ public function __construct( private bool $playgroundMode, // makes all PHPStan classes in the PHAR discoverable with PSR-4 #[AutowiredParameter] private ?string $singleReflectionFile, + private Cache $cache, ) { } @@ -160,13 +163,18 @@ public function create(): SourceLocator ); } } + $cachedPhpstormSourceStubber = new CachedPhpStormStubsSourceStubber( + $this->phpstormStubsSourceStubber, + $this->cache, + $this->phpVersion, + ); $locators[] = new RewriteClassAliasSourceLocator(new AggregateSourceLocator($fileLocators)); - $locators[] = new SkipClassAliasSourceLocator(new PhpInternalSourceLocator($astPhp8Locator, $this->phpstormStubsSourceStubber)); + $locators[] = new SkipClassAliasSourceLocator(new PhpInternalSourceLocator($astPhp8Locator, $cachedPhpstormSourceStubber)); $locators[] = new AutoloadSourceLocator($this->fileNodesFetcher, true); - $locators[] = new PhpVersionBlacklistSourceLocator(new PhpInternalSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber); - $locators[] = new PhpVersionBlacklistSourceLocator(new EvaledCodeSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber); + $locators[] = new PhpVersionBlacklistSourceLocator(new PhpInternalSourceLocator($astLocator, $this->reflectionSourceStubber), $cachedPhpstormSourceStubber); + $locators[] = new PhpVersionBlacklistSourceLocator(new EvaledCodeSourceLocator($astLocator, $this->reflectionSourceStubber), $cachedPhpstormSourceStubber); return new MemoizingSourceLocator(new AggregateSourceLocator($locators)); } diff --git a/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php index 3ff2e8cc96..2bc02e54a3 100644 --- a/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php @@ -9,13 +9,14 @@ use PHPStan\BetterReflection\Reflector\Reflector; use PHPStan\BetterReflection\SourceLocator\SourceStubber\PhpStormStubsSourceStubber; use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator; +use PHPStan\Reflection\BetterReflection\SourceStubber\CachedPhpStormStubsSourceStubber; final class PhpVersionBlacklistSourceLocator implements SourceLocator { public function __construct( - private SourceLocator $sourceLocator, - private PhpStormStubsSourceStubber $phpStormStubsSourceStubber, + private SourceLocator $sourceLocator, + private PhpStormStubsSourceStubber|CachedPhpStormStubsSourceStubber $phpStormStubsSourceStubber, ) { } diff --git a/src/Reflection/BetterReflection/SourceStubber/CachedPhpStormStubsSourceStubber.php b/src/Reflection/BetterReflection/SourceStubber/CachedPhpStormStubsSourceStubber.php new file mode 100644 index 0000000000..fa9923806d --- /dev/null +++ b/src/Reflection/BetterReflection/SourceStubber/CachedPhpStormStubsSourceStubber.php @@ -0,0 +1,98 @@ + */ + private array $cached; + + public function __construct( + private PhpStormStubsSourceStubber $sourceStubber, + private Cache $cache, + private PhpVersion $phpVersion, + ) + { + [$cacheKey, $variableCacheKey] = $this->getCacheKeys(); + $this->cached = $this->cache->load($cacheKey, $variableCacheKey) ?? []; + } + + /** + * @return array{non-empty-string, string} + */ + private function getCacheKeys(): array + { + $stubsVersion = 'dev-master#5fd9e4ef2b4c2a00848ea62d78bf3aa81f43e038'; + $cacheKey = 'phpstorm-stubs'; + $variableCacheKey = sprintf('v1-%s-%s-%s', ComposerHelper::getBetterReflectionVersion(), $this->phpVersion->getVersionString(), $stubsVersion); + + return [$cacheKey, $variableCacheKey]; + } + + public function generateClassStub(string $className): ?StubData + { + $this->cached['classes'] ??= []; + if (!array_key_exists($className, $this->cached['classes'])) { + $this->cached['classes'][$className] = $this->sourceStubber->generateClassStub($className); + $this->storeCache(); + } + return $this->cached['classes'][$className]; + } + + public function generateFunctionStub(string $functionName): ?StubData + { + $this->cached['functions'] ??= []; + if (!array_key_exists($functionName, $this->cached['functions'])) { + $this->cached['functions'][$functionName] = $this->sourceStubber->generateFunctionStub($functionName); + $this->storeCache(); + } + return $this->cached['functions'][$functionName]; + } + + public function generateConstantStub(string $constantName): ?StubData + { + $this->cached['constants'] ??= []; + if (!array_key_exists($constantName, $this->cached['constants'])) { + $this->cached['constants'][$constantName] = $this->sourceStubber->generateConstantStub($constantName); + $this->storeCache(); + } + return $this->cached['constants'][$constantName]; + } + + public function isPresentClass(string $className): ?bool + { + $this->cached['isPresentClass'] ??= []; + if (!array_key_exists($className, $this->cached['isPresentClass'])) { + $this->cached['isPresentClass'][$className] = $this->sourceStubber->isPresentClass($className); + $this->storeCache(); + } + return $this->cached['isPresentClass'][$className]; + } + + public function isPresentFunction(string $functionName): ?bool + { + $this->cached['isPresentFunction'] ??= []; + if (!array_key_exists($functionName, $this->cached['isPresentFunction'])) { + $this->cached['isPresentFunction'][$functionName] = $this->sourceStubber->isPresentFunction($functionName); + $this->storeCache(); + } + return $this->cached['isPresentFunction'][$functionName]; + } + + private function storeCache(): void + { + [$cacheKey, $variableCacheKey] = $this->getCacheKeys(); + $this->cache->save($cacheKey, $variableCacheKey, $this->cached); + } + +} From a18976009d46df9fd5a1137e7c76955581ac5323 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 14 Feb 2026 15:04:21 +0100 Subject: [PATCH 2/6] fix --- src/Internal/ComposerHelper.php | 17 +++++++++++++++++ .../PhpVersionBlacklistSourceLocator.php | 2 +- .../CachedPhpStormStubsSourceStubber.php | 9 ++++++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/Internal/ComposerHelper.php b/src/Internal/ComposerHelper.php index 6a5f518197..b771d87fb6 100644 --- a/src/Internal/ComposerHelper.php +++ b/src/Internal/ComposerHelper.php @@ -23,6 +23,8 @@ final class ComposerHelper private static ?string $betterReflectionVersion = null; + private static ?string $phpstomStubsVersion = null; + private static ?string $phpDocParserVersion = null; /** @var array */ @@ -124,6 +126,21 @@ public static function getBetterReflectionVersion(): string return self::$betterReflectionVersion = self::processPackageVersion($rootPackage); } + public static function getPhpStormStubsVersion(): string + { + if (self::$phpstomStubsVersion !== null) { + return self::$phpstomStubsVersion; + } + + $installed = self::getInstalled(); + $rootPackage = $installed['versions']['jetbrains/phpstorm-stubs'] ?? null; + if ($rootPackage === null) { + return self::$phpstomStubsVersion = self::UNKNOWN_VERSION; + } + + return self::$phpstomStubsVersion = self::processPackageVersion($rootPackage); + } + public static function getPhpDocParserVersion(): string { if (self::$phpDocParserVersion !== null) { diff --git a/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php index 2bc02e54a3..5d6f36c018 100644 --- a/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php @@ -15,7 +15,7 @@ final class PhpVersionBlacklistSourceLocator implements SourceLocator { public function __construct( - private SourceLocator $sourceLocator, + private SourceLocator $sourceLocator, private PhpStormStubsSourceStubber|CachedPhpStormStubsSourceStubber $phpStormStubsSourceStubber, ) { diff --git a/src/Reflection/BetterReflection/SourceStubber/CachedPhpStormStubsSourceStubber.php b/src/Reflection/BetterReflection/SourceStubber/CachedPhpStormStubsSourceStubber.php index fa9923806d..4eaba77695 100644 --- a/src/Reflection/BetterReflection/SourceStubber/CachedPhpStormStubsSourceStubber.php +++ b/src/Reflection/BetterReflection/SourceStubber/CachedPhpStormStubsSourceStubber.php @@ -32,13 +32,14 @@ public function __construct( */ private function getCacheKeys(): array { - $stubsVersion = 'dev-master#5fd9e4ef2b4c2a00848ea62d78bf3aa81f43e038'; - $cacheKey = 'phpstorm-stubs'; - $variableCacheKey = sprintf('v1-%s-%s-%s', ComposerHelper::getBetterReflectionVersion(), $this->phpVersion->getVersionString(), $stubsVersion); + $stubsVersion = ComposerHelper::getPhpStormStubsVersion(); + $cacheKey = sprintf('phpstorm-stubs-%s', $stubsVersion); + $variableCacheKey = sprintf('v1-%s-%s', ComposerHelper::getBetterReflectionVersion(), $this->phpVersion->getVersionString()); return [$cacheKey, $variableCacheKey]; } + #[\Override] public function generateClassStub(string $className): ?StubData { $this->cached['classes'] ??= []; @@ -49,6 +50,7 @@ public function generateClassStub(string $className): ?StubData return $this->cached['classes'][$className]; } + #[\Override] public function generateFunctionStub(string $functionName): ?StubData { $this->cached['functions'] ??= []; @@ -59,6 +61,7 @@ public function generateFunctionStub(string $functionName): ?StubData return $this->cached['functions'][$functionName]; } + #[\Override] public function generateConstantStub(string $constantName): ?StubData { $this->cached['constants'] ??= []; From d3f5f86b2fae47214d47952bd7ace09991b6c00f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 18 Feb 2026 11:15:35 +0100 Subject: [PATCH 3/6] Create FileCachedSourceLocator.php --- .../SourceLocator/FileCachedSourceLocator.php | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/Reflection/BetterReflection/SourceLocator/FileCachedSourceLocator.php diff --git a/src/Reflection/BetterReflection/SourceLocator/FileCachedSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/FileCachedSourceLocator.php new file mode 100644 index 0000000000..596104b9bf --- /dev/null +++ b/src/Reflection/BetterReflection/SourceLocator/FileCachedSourceLocator.php @@ -0,0 +1,107 @@ + */ + private array $cached; + + public function __construct( + private SourceLocator $locator, + private Cache $cache, + private PhpVersion $phpVersion, + private string $cacheKey, + ) + { + $variableCacheKey = $this->getVariableCacheKey(); + $this->cached = $this->cache->load($this->cacheKey, $variableCacheKey) ?? []; + } + + + public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?\PHPStan\BetterReflection\Reflection\Reflection + { + $key = $identifier->getName(); + + $this->cached['identifier'] ??= []; + if (!array_key_exists($key, $this->cached['identifier'])) { + $this->cached['identifier'][$key] = $this->locator->locateIdentifier($reflector, $identifier); + $this->storeCache(); + } + + return $this->cached['identifier'][$key]; + } + + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + { + $key = $identifierType->getName(); + + $this->cached['identifiersByType'] ??= []; + if (!array_key_exists($key, $this->cached['identifiersByType'])) { + $this->cached['identifiersByType'][$key] = $this->locator->locateIdentifiersByType($reflector, $identifierType); + $this->storeCache(); + } + + return $this->cached['identifiersByType'][$key]; + } + + private function getVariableCacheKey(): string + { + return sprintf('v1-%s-%s', ComposerHelper::getBetterReflectionVersion(), $this->phpVersion->getVersionString()); + } + + private function storeCache(): void + { + $variableCacheKey = $this->getVariableCacheKey(); + $this->cache->save($this->cacheKey, $variableCacheKey, $this->cached); + } +} From 08f3a7e22745be04a4ee16e0160ca720a2e4f5ec Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 21 Feb 2026 16:21:53 +0100 Subject: [PATCH 4/6] Update BetterReflectionSourceLocatorFactory.php --- .../BetterReflectionSourceLocatorFactory.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php index 1cee568775..7fac811c82 100644 --- a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php @@ -16,10 +16,12 @@ use PHPStan\Cache\Cache; use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\AutowiredService; +use PHPStan\Internal\ComposerHelper; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\BetterReflection\SourceLocator\AutoloadFunctionsSourceLocator; use PHPStan\Reflection\BetterReflection\SourceLocator\AutoloadSourceLocator; use PHPStan\Reflection\BetterReflection\SourceLocator\ComposerJsonAndInstalledJsonSourceLocatorMaker; +use PHPStan\Reflection\BetterReflection\SourceLocator\FileCachedSourceLocator; use PHPStan\Reflection\BetterReflection\SourceLocator\FileNodesFetcher; use PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedDirectorySourceLocatorRepository; use PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedPsrAutoloaderLocatorFactory; @@ -163,14 +165,18 @@ public function create(): SourceLocator ); } } - $cachedPhpstormSourceStubber = new CachedPhpStormStubsSourceStubber( - $this->phpstormStubsSourceStubber, - $this->cache, - $this->phpVersion, - ); + + $phpstormStubsVersion = ComposerHelper::getPhpStormStubsVersion(); $locators[] = new RewriteClassAliasSourceLocator(new AggregateSourceLocator($fileLocators)); - $locators[] = new SkipClassAliasSourceLocator(new PhpInternalSourceLocator($astPhp8Locator, $cachedPhpstormSourceStubber)); + $locators[] = new SkipClassAliasSourceLocator( + new FileCachedSourceLocator( + new PhpInternalSourceLocator($astPhp8Locator, $this->phpstormStubsSourceStubber), + $this->cache, + $this->phpVersion, + sprintf('phpstorm-stubs-php8-%s', $phpstormStubsVersion) + ) + ); $locators[] = new AutoloadSourceLocator($this->fileNodesFetcher, true); $locators[] = new PhpVersionBlacklistSourceLocator(new PhpInternalSourceLocator($astLocator, $this->reflectionSourceStubber), $cachedPhpstormSourceStubber); From 805a07882bc65e7e97e4324881c5b2c01ed8c4ca Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 21 Feb 2026 17:43:32 +0100 Subject: [PATCH 5/6] rework --- .../BetterReflectionSourceLocatorFactory.php | 10 +- .../SourceLocator/FileCachedSourceLocator.php | 174 ++++++++++++------ .../PhpVersionBlacklistSourceLocator.php | 3 +- .../CachedPhpStormStubsSourceStubber.php | 101 ---------- 4 files changed, 122 insertions(+), 166 deletions(-) delete mode 100644 src/Reflection/BetterReflection/SourceStubber/CachedPhpStormStubsSourceStubber.php diff --git a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php index 7fac811c82..f2c367d9a5 100644 --- a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php @@ -31,13 +31,13 @@ use PHPStan\Reflection\BetterReflection\SourceLocator\RewriteClassAliasSourceLocator; use PHPStan\Reflection\BetterReflection\SourceLocator\SkipClassAliasSourceLocator; use PHPStan\Reflection\BetterReflection\SourceLocator\SkipPolyfillSourceLocator; -use PHPStan\Reflection\BetterReflection\SourceStubber\CachedPhpStormStubsSourceStubber; use function array_merge; use function array_unique; use function count; use function extension_loaded; use function is_dir; use function is_file; +use function sprintf; use const PHP_VERSION_ID; #[AutowiredService] @@ -174,13 +174,13 @@ public function create(): SourceLocator new PhpInternalSourceLocator($astPhp8Locator, $this->phpstormStubsSourceStubber), $this->cache, $this->phpVersion, - sprintf('phpstorm-stubs-php8-%s', $phpstormStubsVersion) - ) + sprintf('phpstorm-stubs-php8-%s', $phpstormStubsVersion), + ), ); $locators[] = new AutoloadSourceLocator($this->fileNodesFetcher, true); - $locators[] = new PhpVersionBlacklistSourceLocator(new PhpInternalSourceLocator($astLocator, $this->reflectionSourceStubber), $cachedPhpstormSourceStubber); - $locators[] = new PhpVersionBlacklistSourceLocator(new EvaledCodeSourceLocator($astLocator, $this->reflectionSourceStubber), $cachedPhpstormSourceStubber); + $locators[] = new PhpVersionBlacklistSourceLocator(new PhpInternalSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber); + $locators[] = new PhpVersionBlacklistSourceLocator(new EvaledCodeSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber); return new MemoizingSourceLocator(new AggregateSourceLocator($locators)); } diff --git a/src/Reflection/BetterReflection/SourceLocator/FileCachedSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/FileCachedSourceLocator.php index 596104b9bf..bf523b5e1b 100644 --- a/src/Reflection/BetterReflection/SourceLocator/FileCachedSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/FileCachedSourceLocator.php @@ -2,59 +2,31 @@ namespace PHPStan\Reflection\BetterReflection\SourceLocator; -use Override; -use PhpParser\Node\Arg; -use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Name; -use PhpParser\Node\Scalar\String_; -use PhpParser\Node\Stmt\Const_; use PHPStan\BetterReflection\Identifier\Identifier; use PHPStan\BetterReflection\Identifier\IdentifierType; use PHPStan\BetterReflection\Reflection\Reflection; +use PHPStan\BetterReflection\Reflection\ReflectionClass; use PHPStan\BetterReflection\Reflection\ReflectionConstant; +use PHPStan\BetterReflection\Reflection\ReflectionEnum; +use PHPStan\BetterReflection\Reflection\ReflectionFunction; use PHPStan\BetterReflection\Reflector\Reflector; -use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection; -use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource; use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator; use PHPStan\Cache\Cache; use PHPStan\Internal\ComposerHelper; -use PHPStan\Node\Expr\TypeExpr; use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ConstantNameHelper; -use PHPStan\ShouldNotHappenException; -use PHPStan\Type\ConstantTypeHelper; -use ReflectionClass; -use ReflectionFunction; use function array_key_exists; -use function array_keys; -use function class_exists; -use function constant; -use function count; -use function defined; -use function function_exists; -use function interface_exists; -use function is_file; -use function is_string; -use function opcache_invalidate; -use function restore_error_handler; -use function set_error_handler; -use function spl_autoload_functions; +use function register_shutdown_function; +use function sprintf; use function strtolower; -use function trait_exists; -use const PHP_VERSION_ID; - -/** - * Use PHP's built in autoloader to locate a class, without actually loading. - * - * There are some prerequisites... - * - we expect the autoloader to load classes from a file (i.e. using require/include) - * - * Modified code from Roave/BetterReflection, Copyright (c) 2017 Roave, LLC. - */ + final class FileCachedSourceLocator implements SourceLocator { - /** @var array */ - private array $cached; + + /** @var array{classes: array, functions: array, constants: array}|null */ + private ?array $cachedSymbols = null; + + private bool $storeOnShutdown = false; public function __construct( private SourceLocator $locator, @@ -63,45 +35,131 @@ public function __construct( private string $cacheKey, ) { - $variableCacheKey = $this->getVariableCacheKey(); - $this->cached = $this->cache->load($this->cacheKey, $variableCacheKey) ?? []; } - - public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?\PHPStan\BetterReflection\Reflection\Reflection + public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection { - $key = $identifier->getName(); + if ($this->cachedSymbols === null) { + $this->cachedSymbols = $this->loadCache($reflector); + } + + if ($identifier->isClass()) { + $className = strtolower($identifier->getName()); + + if (!array_key_exists($className, $this->cachedSymbols['classes'])) { + $this->cachedSymbols['classes'][$className] ??= $this->locator->locateIdentifier($reflector, $identifier); + $this->storeOnShutdown(); + } + return $this->cachedSymbols['classes'][$className]; + } + if ($identifier->isFunction()) { + $className = strtolower($identifier->getName()); + + if (!array_key_exists($className, $this->cachedSymbols['functions'])) { + $this->cachedSymbols['functions'][$className] ??= $this->locator->locateIdentifier($reflector, $identifier); + $this->storeOnShutdown(); + } + return $this->cachedSymbols['functions'][$className]; + } + if ($identifier->isConstant()) { + $constantName = ConstantNameHelper::normalize($identifier->getName()); - $this->cached['identifier'] ??= []; - if (!array_key_exists($key, $this->cached['identifier'])) { - $this->cached['identifier'][$key] = $this->locator->locateIdentifier($reflector, $identifier); - $this->storeCache(); + if (!array_key_exists($constantName, $this->cachedSymbols['constants'])) { + $this->cachedSymbols['constants'][$constantName] ??= $this->locator->locateIdentifier($reflector, $identifier); + $this->storeOnShutdown(); + } + return $this->cachedSymbols['constants'][$constantName]; } - return $this->cached['identifier'][$key]; + return null; } public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array { - $key = $identifierType->getName(); + return $this->locator->locateIdentifiersByType($reflector, $identifierType); + } + + private function getVariableCacheKey(): string + { + return sprintf('v2-%s-%s', ComposerHelper::getBetterReflectionVersion(), $this->phpVersion->getVersionString()); + } - $this->cached['identifiersByType'] ??= []; - if (!array_key_exists($key, $this->cached['identifiersByType'])) { - $this->cached['identifiersByType'][$key] = $this->locator->locateIdentifiersByType($reflector, $identifierType); - $this->storeCache(); + private function storeOnShutdown(): void + { + if ($this->storeOnShutdown) { + return; } - return $this->cached['identifiersByType'][$key]; + $this->storeOnShutdown = true; + register_shutdown_function([$this, 'storeCache']); } - private function getVariableCacheKey(): string + /** @return array{classes: array, functions: array, constants: array} */ + private function loadCache(Reflector $reflector): array { - return sprintf('v1-%s-%s', ComposerHelper::getBetterReflectionVersion(), $this->phpVersion->getVersionString()); + $variableCacheKey = $this->getVariableCacheKey(); + $cached = $this->cache->load($this->cacheKey, $variableCacheKey); + + $restored = [ + 'classes' => [], + 'functions' => [], + 'constants' => [], + ]; + if ($cached === null) { + return $restored; + } + + foreach ($cached['classes'] ?? [] as $class => $cachedReflection) { + if ($cachedReflection === null) { + $restored['classes'][$class] = null; + continue; + } + + if (array_key_exists('backingType', $cachedReflection)) { + $restored['classes'][$class] = ReflectionEnum::importFromCache($reflector, $cachedReflection); + continue; + } + + $restored['classes'][$class] = ReflectionClass::importFromCache($reflector, $cachedReflection); + } + foreach ($cached['functions'] ?? [] as $class => $cachedReflection) { + if ($cachedReflection === null) { + $restored['functions'][$class] = null; + continue; + } + $restored['functions'][$class] = ReflectionFunction::importFromCache($reflector, $cachedReflection); + } + foreach ($cached['constants'] ?? [] as $constantName => $cachedReflection) { + if ($cachedReflection === null) { + $restored['constants'][$constantName] = null; + continue; + } + + $restored['constants'][$constantName] = ReflectionConstant::importFromCache($reflector, $cachedReflection); + } + return $restored; } private function storeCache(): void { $variableCacheKey = $this->getVariableCacheKey(); - $this->cache->save($this->cacheKey, $variableCacheKey, $this->cached); + + $exported = [ + 'classes' => [], + 'functions' => [], + 'constants' => [], + ]; + foreach ($this->cachedSymbols ?? [] as $type => $data) { + foreach ($data as $name => $reflection) { + if ($reflection === null) { + $exported[$type][$name] = $reflection; + continue; + } + $exported[$type][$name] = $reflection->exportToCache(); + } + } + + $this->cache->save($this->cacheKey, $variableCacheKey, $exported); } + } diff --git a/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php index 5d6f36c018..3ff2e8cc96 100644 --- a/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/PhpVersionBlacklistSourceLocator.php @@ -9,14 +9,13 @@ use PHPStan\BetterReflection\Reflector\Reflector; use PHPStan\BetterReflection\SourceLocator\SourceStubber\PhpStormStubsSourceStubber; use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator; -use PHPStan\Reflection\BetterReflection\SourceStubber\CachedPhpStormStubsSourceStubber; final class PhpVersionBlacklistSourceLocator implements SourceLocator { public function __construct( private SourceLocator $sourceLocator, - private PhpStormStubsSourceStubber|CachedPhpStormStubsSourceStubber $phpStormStubsSourceStubber, + private PhpStormStubsSourceStubber $phpStormStubsSourceStubber, ) { } diff --git a/src/Reflection/BetterReflection/SourceStubber/CachedPhpStormStubsSourceStubber.php b/src/Reflection/BetterReflection/SourceStubber/CachedPhpStormStubsSourceStubber.php deleted file mode 100644 index 4eaba77695..0000000000 --- a/src/Reflection/BetterReflection/SourceStubber/CachedPhpStormStubsSourceStubber.php +++ /dev/null @@ -1,101 +0,0 @@ - */ - private array $cached; - - public function __construct( - private PhpStormStubsSourceStubber $sourceStubber, - private Cache $cache, - private PhpVersion $phpVersion, - ) - { - [$cacheKey, $variableCacheKey] = $this->getCacheKeys(); - $this->cached = $this->cache->load($cacheKey, $variableCacheKey) ?? []; - } - - /** - * @return array{non-empty-string, string} - */ - private function getCacheKeys(): array - { - $stubsVersion = ComposerHelper::getPhpStormStubsVersion(); - $cacheKey = sprintf('phpstorm-stubs-%s', $stubsVersion); - $variableCacheKey = sprintf('v1-%s-%s', ComposerHelper::getBetterReflectionVersion(), $this->phpVersion->getVersionString()); - - return [$cacheKey, $variableCacheKey]; - } - - #[\Override] - public function generateClassStub(string $className): ?StubData - { - $this->cached['classes'] ??= []; - if (!array_key_exists($className, $this->cached['classes'])) { - $this->cached['classes'][$className] = $this->sourceStubber->generateClassStub($className); - $this->storeCache(); - } - return $this->cached['classes'][$className]; - } - - #[\Override] - public function generateFunctionStub(string $functionName): ?StubData - { - $this->cached['functions'] ??= []; - if (!array_key_exists($functionName, $this->cached['functions'])) { - $this->cached['functions'][$functionName] = $this->sourceStubber->generateFunctionStub($functionName); - $this->storeCache(); - } - return $this->cached['functions'][$functionName]; - } - - #[\Override] - public function generateConstantStub(string $constantName): ?StubData - { - $this->cached['constants'] ??= []; - if (!array_key_exists($constantName, $this->cached['constants'])) { - $this->cached['constants'][$constantName] = $this->sourceStubber->generateConstantStub($constantName); - $this->storeCache(); - } - return $this->cached['constants'][$constantName]; - } - - public function isPresentClass(string $className): ?bool - { - $this->cached['isPresentClass'] ??= []; - if (!array_key_exists($className, $this->cached['isPresentClass'])) { - $this->cached['isPresentClass'][$className] = $this->sourceStubber->isPresentClass($className); - $this->storeCache(); - } - return $this->cached['isPresentClass'][$className]; - } - - public function isPresentFunction(string $functionName): ?bool - { - $this->cached['isPresentFunction'] ??= []; - if (!array_key_exists($functionName, $this->cached['isPresentFunction'])) { - $this->cached['isPresentFunction'][$functionName] = $this->sourceStubber->isPresentFunction($functionName); - $this->storeCache(); - } - return $this->cached['isPresentFunction'][$functionName]; - } - - private function storeCache(): void - { - [$cacheKey, $variableCacheKey] = $this->getCacheKeys(); - $this->cache->save($cacheKey, $variableCacheKey, $this->cached); - } - -} From c5471c415c1e4e0d253135b745c6ba306d0ca615 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 21 Feb 2026 17:55:42 +0100 Subject: [PATCH 6/6] Update FileCachedSourceLocator.php --- .../SourceLocator/FileCachedSourceLocator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Reflection/BetterReflection/SourceLocator/FileCachedSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/FileCachedSourceLocator.php index bf523b5e1b..03f314020e 100644 --- a/src/Reflection/BetterReflection/SourceLocator/FileCachedSourceLocator.php +++ b/src/Reflection/BetterReflection/SourceLocator/FileCachedSourceLocator.php @@ -37,11 +37,10 @@ public function __construct( { } + #[\Override] public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection { - if ($this->cachedSymbols === null) { - $this->cachedSymbols = $this->loadCache($reflector); - } + $this->cachedSymbols ??= $this->loadCache($reflector); if ($identifier->isClass()) { $className = strtolower($identifier->getName()); @@ -74,6 +73,7 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier): return null; } + #[\Override] public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array { return $this->locator->locateIdentifiersByType($reflector, $identifierType);