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..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..5d6f36c018 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 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..4eaba77695 --- /dev/null +++ b/src/Reflection/BetterReflection/SourceStubber/CachedPhpStormStubsSourceStubber.php @@ -0,0 +1,101 @@ + */ + 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); + } + +}