diff --git a/src/Application/ApplicationFileProcessor.php b/src/Application/ApplicationFileProcessor.php index 5588508074b..4d6416b1c61 100644 --- a/src/Application/ApplicationFileProcessor.php +++ b/src/Application/ApplicationFileProcessor.php @@ -5,10 +5,13 @@ namespace Rector\Application; use Nette\Utils\FileSystem as UtilsFileSystem; +use Rector\Caching\Cache; use Rector\Caching\Detector\ChangedFilesDetector; +use Rector\Caching\Enum\CacheKey; use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; use Rector\Configuration\VendorMissAnalyseGuard; +use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider; use Rector\Parallel\Application\ParallelFileProcessor; use Rector\Provider\CurrentFileProvider; use Rector\Testing\PHPUnit\StaticPHPUnitEnvironment; @@ -50,6 +53,8 @@ public function __construct( private readonly FileProcessor $fileProcessor, private readonly ArrayParametersMerger $arrayParametersMerger, private readonly VendorMissAnalyseGuard $vendorMissAnalyseGuard, + private readonly DynamicSourceLocatorProvider $dynamicSourceLocatorProvider, + private readonly Cache $cache ) { } @@ -71,6 +76,10 @@ public function run(Configuration $configuration, InputInterface $input): Proces return new ProcessResult([], []); } + // ensure clear classnames collection caches on repetitive call + $key = CacheKey::CLASSNAMES_HASH_KEY . '_' . $this->dynamicSourceLocatorProvider->getCacheClassNameKey(); + $this->cache->clean($key); + $this->configureCustomErrorHandler(); /** @@ -98,6 +107,9 @@ public function run(Configuration $configuration, InputInterface $input): Proces $preFileCallback = null; } + // trigger cache class names collection + $this->dynamicSourceLocatorProvider->provide(); + if ($configuration->isParallel()) { $processResult = $this->runParallel($filePaths, $configuration, $input, $postFileCallback); } else { diff --git a/src/Caching/Enum/CacheKey.php b/src/Caching/Enum/CacheKey.php index 66512c05905..df5df985508 100644 --- a/src/Caching/Enum/CacheKey.php +++ b/src/Caching/Enum/CacheKey.php @@ -18,4 +18,9 @@ final class CacheKey * @var string */ public const FILE_HASH_KEY = 'file_hash'; + + /** + * @var string + */ + public const CLASSNAMES_HASH_KEY = 'classnames_hash'; } diff --git a/src/Console/Command/ProcessCommand.php b/src/Console/Command/ProcessCommand.php index 5c2b9655289..502b9425bab 100644 --- a/src/Console/Command/ProcessCommand.php +++ b/src/Console/Command/ProcessCommand.php @@ -36,7 +36,7 @@ public function __construct( private readonly OutputFormatterCollector $outputFormatterCollector, private readonly SymfonyStyle $symfonyStyle, private readonly MemoryLimiter $memoryLimiter, - private readonly ConfigurationFactory $configurationFactory, + private readonly ConfigurationFactory $configurationFactory ) { parent::__construct(); } diff --git a/src/DependencyInjection/LazyContainerFactory.php b/src/DependencyInjection/LazyContainerFactory.php index 10472589b8a..1ffc1937905 100644 --- a/src/DependencyInjection/LazyContainerFactory.php +++ b/src/DependencyInjection/LazyContainerFactory.php @@ -180,6 +180,7 @@ use Rector\StaticTypeMapper\PhpParser\StringNodeMapper; use Rector\StaticTypeMapper\PhpParser\UnionTypeNodeMapper; use Rector\StaticTypeMapper\StaticTypeMapper; +use Rector\Util\FileHasher; use Rector\Utils\Command\MissingInSetCommand; use Rector\Utils\Command\OutsideAnySetCommand; use Symfony\Component\Console\Application; @@ -469,7 +470,11 @@ static function (Container $container): DynamicSourceLocatorProvider { $rectorConfig->afterResolving( DynamicSourceLocatorProvider::class, static function (DynamicSourceLocatorProvider $dynamicSourceLocatorProvider, Container $container): void { - $dynamicSourceLocatorProvider->autowire($container->make(ReflectionProvider::class)); + $dynamicSourceLocatorProvider->autowire( + $container->make(ReflectionProvider::class), + $container->make(Cache::class), + $container->make(FileHasher::class) + ); } ); diff --git a/src/NodeTypeResolver/Reflection/BetterReflection/SourceLocatorProvider/DynamicSourceLocatorProvider.php b/src/NodeTypeResolver/Reflection/BetterReflection/SourceLocatorProvider/DynamicSourceLocatorProvider.php index 165e1868db5..014ae82ec24 100644 --- a/src/NodeTypeResolver/Reflection/BetterReflection/SourceLocatorProvider/DynamicSourceLocatorProvider.php +++ b/src/NodeTypeResolver/Reflection/BetterReflection/SourceLocatorProvider/DynamicSourceLocatorProvider.php @@ -14,8 +14,11 @@ use PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedDirectorySourceLocatorFactory; use PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedSingleFileSourceLocator; use PHPStan\Reflection\ReflectionProvider; +use Rector\Caching\Cache; +use Rector\Caching\Enum\CacheKey; use Rector\Contract\DependencyInjection\ResetableInterface; use Rector\Testing\PHPUnit\StaticPHPUnitEnvironment; +use Rector\Util\FileHasher; /** * @api phpstan external @@ -36,15 +39,25 @@ final class DynamicSourceLocatorProvider implements ResetableInterface private ReflectionProvider $reflectionProvider; + private Cache $cache; + + private FileHasher $fileHasher; + public function __construct( private readonly FileNodesFetcher $fileNodesFetcher, private readonly OptimizedDirectorySourceLocatorFactory $optimizedDirectorySourceLocatorFactory ) { } - public function autowire(ReflectionProvider $reflectionProvider): void + public function autowire( + ReflectionProvider $reflectionProvider, + Cache $cache, + FileHasher $fileHasher + ): void { $this->reflectionProvider = $reflectionProvider; + $this->cache = $cache; + $this->fileHasher = $fileHasher; } public function setFilePath(string $filePath): void @@ -52,6 +65,22 @@ public function setFilePath(string $filePath): void $this->filePaths = [$filePath]; } + public function getCacheClassNameKey(): string + { + $paths = []; + + foreach ($this->filePaths as $filePath) { + $paths[] = (string) realpath($filePath); + } + + foreach ($this->directories as $directory) { + $paths[] = (string) realpath($directory); + } + + $paths = array_filter($paths); + return CacheKey::CLASSNAMES_HASH_KEY . '_' . $this->fileHasher->hash((string) json_encode($paths)); + } + /** * @param string[] $files */ @@ -109,6 +138,19 @@ public function reset(): void $this->aggregateSourceLocator = null; } + /** + * @param class-string[] $classNamesCache + */ + private function locateCachedClassNames(array $classNamesCache): void + { + foreach ($classNamesCache as $classNameCache) { + try { + $this->reflectionProvider->getClass($classNameCache); + } catch (ClassNotFoundException) { + } + } + } + /** * @param OptimizedSingleFileSourceLocator[]|NewOptimizedDirectorySourceLocator[] $sourceLocators */ @@ -123,19 +165,38 @@ private function collectClasses(AggregateSourceLocator $aggregateSourceLocator, return; } + $key = CacheKey::CLASSNAMES_HASH_KEY . '_' . $this->getCacheClassNameKey(); + $classNamesCache = $this->cache->load($key, CacheKey::CLASSNAMES_HASH_KEY); + + if (is_string($classNamesCache)) { + $classNamesCache = json_decode($classNamesCache); + if (is_array($classNamesCache)) { + $this->locateCachedClassNames($classNamesCache); + return; + } + } + $reflector = new DefaultReflector($aggregateSourceLocator); + $classNames = []; // trigger collect "classes" on get class on locate identifier try { $reflections = $reflector->reflectAllClasses(); foreach ($reflections as $reflection) { + $className = $reflection->getName(); + // make 'classes' collection try { - $this->reflectionProvider->getClass($reflection->getName()); + $this->reflectionProvider->getClass($className); } catch (ClassNotFoundException) { + continue; } + + $classNames[] = $className; } } catch (CouldNotReadFileException) { } + + $this->cache->save($key, CacheKey::CLASSNAMES_HASH_KEY, json_encode($classNames)); } }