diff --git a/extension.neon b/extension.neon index f631ec5e..d0cb97c9 100644 --- a/extension.neon +++ b/extension.neon @@ -310,5 +310,8 @@ services: - class: PHPStan\Symfony\SymfonyContainerResultCacheMetaExtension + arguments: + tmpDir: %tmpDir% + containerXmlPath: %symfony.containerXmlPath% tags: - phpstan.resultCacheMetaExtension diff --git a/src/Symfony/SymfonyContainerResultCacheMetaExtension.php b/src/Symfony/SymfonyContainerResultCacheMetaExtension.php index 8e2f8028..88ebbe3c 100644 --- a/src/Symfony/SymfonyContainerResultCacheMetaExtension.php +++ b/src/Symfony/SymfonyContainerResultCacheMetaExtension.php @@ -4,9 +4,13 @@ use PHPStan\Analyser\ResultCache\ResultCacheMetaExtension; use function array_map; +use function file_get_contents; +use function file_put_contents; use function hash; +use function hash_file; use function ksort; use function sort; +use function sprintf; use function var_export; final class SymfonyContainerResultCacheMetaExtension implements ResultCacheMetaExtension @@ -16,10 +20,16 @@ final class SymfonyContainerResultCacheMetaExtension implements ResultCacheMetaE private ServiceMap $serviceMap; - public function __construct(ParameterMap $parameterMap, ServiceMap $serviceMap) + private string $tmpDir; + + private ?string $containerXmlPath; + + public function __construct(ParameterMap $parameterMap, ServiceMap $serviceMap, string $tmpDir, ?string $containerXmlPath) { $this->parameterMap = $parameterMap; $this->serviceMap = $serviceMap; + $this->tmpDir = $tmpDir; + $this->containerXmlPath = $containerXmlPath; } public function getKey(): string @@ -28,6 +38,29 @@ public function getKey(): string } public function getHash(): string + { + if ($this->containerXmlPath !== null) { + $xmlHash = hash_file('sha256', $this->containerXmlPath); + if ($xmlHash === false) { + throw new XmlContainerNotExistsException(sprintf('Container %s does not exist', $this->containerXmlPath)); + } + $hashFile = sprintf('%s/%s-%s.hash', $this->tmpDir, $this->getKey(), $xmlHash); + $hash = @file_get_contents($hashFile); + if ($hash !== false) { + return $hash; + } + } + + $hash = $this->calculateHash(); + + if ($this->containerXmlPath !== null) { + file_put_contents($hashFile, $hash); + } + + return $hash; + } + + public function calculateHash(): string { $services = $parameters = []; diff --git a/tests/Symfony/SymfonyContainerResultCacheMetaExtensionTest.php b/tests/Symfony/SymfonyContainerResultCacheMetaExtensionTest.php index f5c8503f..79c194d3 100644 --- a/tests/Symfony/SymfonyContainerResultCacheMetaExtensionTest.php +++ b/tests/Symfony/SymfonyContainerResultCacheMetaExtensionTest.php @@ -4,6 +4,15 @@ use PHPStan\Testing\PHPStanTestCase; use function count; +use function file_get_contents; +use function file_put_contents; +use function glob; +use function mkdir; +use function rmdir; +use function sprintf; +use function sys_get_temp_dir; +use function uniqid; +use function unlink; /** * @phpstan-type ContainerContents array{parameters?: ParameterMap, services?: ServiceMap} @@ -11,6 +20,61 @@ final class SymfonyContainerResultCacheMetaExtensionTest extends PHPStanTestCase { + private string $tmpDir; + + protected function setUp(): void + { + parent::setUp(); + $this->tmpDir = sys_get_temp_dir() . '/phpstan-symfony-test-' . uniqid('', true); + mkdir($this->tmpDir, 0777, true); + } + + protected function tearDown(): void + { + $cacheFiles = glob($this->tmpDir . '/*.hash'); + if ($cacheFiles !== false) { + foreach ($cacheFiles as $file) { + unlink($file); + } + } + rmdir($this->tmpDir); + parent::tearDown(); + } + + public function testHashIsCalculatedAndWrittenToCacheFileOnCacheMiss(): void + { + $containerXmlPath = __DIR__ . '/container.xml'; + + $extension = new SymfonyContainerResultCacheMetaExtension( + new DefaultParameterMap([]), + new DefaultServiceMap([]), + $this->tmpDir, + $containerXmlPath, + ); + + $hash = $extension->getHash(); + + $cacheFile = sprintf('%s/symfonyDiContainer-c55d6ac45b535d6ecc9402cbb93825c38ec7b11b03f66577d0d3549b3d9ef75f.hash', $this->tmpDir); + self::assertFileExists($cacheFile); + self::assertSame($hash, file_get_contents($cacheFile)); + } + + public function testCachedHashIsReturnedOnCacheHit(): void + { + $containerXmlPath = __DIR__ . '/container.xml'; + $cacheFile = sprintf('%s/symfonyDiContainer-c55d6ac45b535d6ecc9402cbb93825c38ec7b11b03f66577d0d3549b3d9ef75f.hash', $this->tmpDir); + file_put_contents($cacheFile, 'pre-computed-hash'); + + $extension = new SymfonyContainerResultCacheMetaExtension( + new DefaultParameterMap([]), + new DefaultServiceMap([]), + $this->tmpDir, + $containerXmlPath, + ); + + self::assertSame('pre-computed-hash', $extension->getHash()); + } + /** * @param list $sameHashContents * @param ContainerContents $invalidatingContent @@ -30,6 +94,8 @@ public function testContainerHashIsCalculatedCorrectly( $currentHash = (new SymfonyContainerResultCacheMetaExtension( $content['parameters'] ?? new DefaultParameterMap([]), $content['services'] ?? new DefaultServiceMap([]), + __DIR__ . '/../../tmp', + null, ))->getHash(); if ($hash === null) { @@ -44,6 +110,8 @@ public function testContainerHashIsCalculatedCorrectly( (new SymfonyContainerResultCacheMetaExtension( $invalidatingContent['parameters'] ?? new DefaultParameterMap([]), $invalidatingContent['services'] ?? new DefaultServiceMap([]), + __DIR__ . '/../../tmp', + null, ))->getHash(), ); }