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/BetterReflectionSourceLocatorFactory.php b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php index ddffaffc89..f2c367d9a5 100644 --- a/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php +++ b/src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php @@ -13,12 +13,15 @@ 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\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; @@ -34,6 +37,7 @@ use function extension_loaded; use function is_dir; use function is_file; +use function sprintf; use const PHP_VERSION_ID; #[AutowiredService] @@ -74,6 +78,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, ) { } @@ -161,8 +166,17 @@ public function create(): SourceLocator } } + $phpstormStubsVersion = ComposerHelper::getPhpStormStubsVersion(); + $locators[] = new RewriteClassAliasSourceLocator(new AggregateSourceLocator($fileLocators)); - $locators[] = new SkipClassAliasSourceLocator(new PhpInternalSourceLocator($astPhp8Locator, $this->phpstormStubsSourceStubber)); + $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), $this->phpstormStubsSourceStubber); diff --git a/src/Reflection/BetterReflection/SourceLocator/FileCachedSourceLocator.php b/src/Reflection/BetterReflection/SourceLocator/FileCachedSourceLocator.php new file mode 100644 index 0000000000..03f314020e --- /dev/null +++ b/src/Reflection/BetterReflection/SourceLocator/FileCachedSourceLocator.php @@ -0,0 +1,165 @@ +, functions: array, constants: array}|null */ + private ?array $cachedSymbols = null; + + private bool $storeOnShutdown = false; + + public function __construct( + private SourceLocator $locator, + private Cache $cache, + private PhpVersion $phpVersion, + private string $cacheKey, + ) + { + } + + #[\Override] + public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection + { + $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()); + + 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 null; + } + + #[\Override] + public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array + { + return $this->locator->locateIdentifiersByType($reflector, $identifierType); + } + + private function getVariableCacheKey(): string + { + return sprintf('v2-%s-%s', ComposerHelper::getBetterReflectionVersion(), $this->phpVersion->getVersionString()); + } + + private function storeOnShutdown(): void + { + if ($this->storeOnShutdown) { + return; + } + + $this->storeOnShutdown = true; + register_shutdown_function([$this, 'storeCache']); + } + + /** @return array{classes: array, functions: array, constants: array} */ + private function loadCache(Reflector $reflector): array + { + $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(); + + $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); + } + +}