Skip to content

Commit c34e0fc

Browse files
xificurkclaude
andcommitted
SymfonyContainerResultCacheMetaExtension optimization
Add simple filesytem cache for the DI container hash to avoid expensive parsing, normalizing and hashing of the whole DI container on each call. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 809c1e6 commit c34e0fc

3 files changed

Lines changed: 105 additions & 1 deletion

File tree

extension.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,5 +310,8 @@ services:
310310

311311
-
312312
class: PHPStan\Symfony\SymfonyContainerResultCacheMetaExtension
313+
arguments:
314+
tmpDir: %tmpDir%
315+
containerXmlPath: %symfony.containerXmlPath%
313316
tags:
314317
- phpstan.resultCacheMetaExtension

src/Symfony/SymfonyContainerResultCacheMetaExtension.php

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44

55
use PHPStan\Analyser\ResultCache\ResultCacheMetaExtension;
66
use function array_map;
7+
use function file_get_contents;
8+
use function file_put_contents;
79
use function hash;
10+
use function hash_file;
811
use function ksort;
912
use function sort;
13+
use function sprintf;
1014
use function var_export;
1115

1216
final class SymfonyContainerResultCacheMetaExtension implements ResultCacheMetaExtension
@@ -16,10 +20,16 @@ final class SymfonyContainerResultCacheMetaExtension implements ResultCacheMetaE
1620

1721
private ServiceMap $serviceMap;
1822

19-
public function __construct(ParameterMap $parameterMap, ServiceMap $serviceMap)
23+
private string $tmpDir;
24+
25+
private ?string $containerXmlPath;
26+
27+
public function __construct(ParameterMap $parameterMap, ServiceMap $serviceMap, string $tmpDir, ?string $containerXmlPath)
2028
{
2129
$this->parameterMap = $parameterMap;
2230
$this->serviceMap = $serviceMap;
31+
$this->tmpDir = $tmpDir;
32+
$this->containerXmlPath = $containerXmlPath;
2333
}
2434

2535
public function getKey(): string
@@ -28,6 +38,29 @@ public function getKey(): string
2838
}
2939

3040
public function getHash(): string
41+
{
42+
if ($this->containerXmlPath !== null) {
43+
$xmlHash = hash_file('sha256', $this->containerXmlPath);
44+
if ($xmlHash === false) {
45+
throw new XmlContainerNotExistsException(sprintf('Container %s does not exist', $this->containerXmlPath));
46+
}
47+
$hashFile = sprintf('%s/%s-%s.hash', $this->tmpDir, $this->getKey(), $xmlHash);
48+
$hash = @file_get_contents($hashFile);
49+
if ($hash !== false) {
50+
return $hash;
51+
}
52+
}
53+
54+
$hash = $this->calculateHash();
55+
56+
if ($this->containerXmlPath !== null) {
57+
file_put_contents($hashFile, $hash);
58+
}
59+
60+
return $hash;
61+
}
62+
63+
public function calculateHash(): string
3164
{
3265
$services = $parameters = [];
3366

tests/Symfony/SymfonyContainerResultCacheMetaExtensionTest.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,77 @@
44

55
use PHPStan\Testing\PHPStanTestCase;
66
use function count;
7+
use function file_get_contents;
8+
use function file_put_contents;
9+
use function glob;
10+
use function mkdir;
11+
use function rmdir;
12+
use function sprintf;
13+
use function sys_get_temp_dir;
14+
use function uniqid;
15+
use function unlink;
716

817
/**
918
* @phpstan-type ContainerContents array{parameters?: ParameterMap, services?: ServiceMap}
1019
*/
1120
final class SymfonyContainerResultCacheMetaExtensionTest extends PHPStanTestCase
1221
{
1322

23+
private string $tmpDir;
24+
25+
protected function setUp(): void
26+
{
27+
parent::setUp();
28+
$this->tmpDir = sys_get_temp_dir() . '/phpstan-symfony-test-' . uniqid('', true);
29+
mkdir($this->tmpDir, 0777, true);
30+
}
31+
32+
protected function tearDown(): void
33+
{
34+
$cacheFiles = glob($this->tmpDir . '/*.hash');
35+
if ($cacheFiles !== false) {
36+
foreach ($cacheFiles as $file) {
37+
unlink($file);
38+
}
39+
}
40+
rmdir($this->tmpDir);
41+
parent::tearDown();
42+
}
43+
44+
public function testHashIsCalculatedAndWrittenToCacheFileOnCacheMiss(): void
45+
{
46+
$containerXmlPath = __DIR__ . '/container.xml';
47+
48+
$extension = new SymfonyContainerResultCacheMetaExtension(
49+
new DefaultParameterMap([]),
50+
new DefaultServiceMap([]),
51+
$this->tmpDir,
52+
$containerXmlPath,
53+
);
54+
55+
$hash = $extension->getHash();
56+
57+
$cacheFile = sprintf('%s/symfonyDiContainer-c55d6ac45b535d6ecc9402cbb93825c38ec7b11b03f66577d0d3549b3d9ef75f.hash', $this->tmpDir);
58+
self::assertFileExists($cacheFile);
59+
self::assertSame($hash, file_get_contents($cacheFile));
60+
}
61+
62+
public function testCachedHashIsReturnedOnCacheHit(): void
63+
{
64+
$containerXmlPath = __DIR__ . '/container.xml';
65+
$cacheFile = sprintf('%s/symfonyDiContainer-c55d6ac45b535d6ecc9402cbb93825c38ec7b11b03f66577d0d3549b3d9ef75f.hash', $this->tmpDir);
66+
file_put_contents($cacheFile, 'pre-computed-hash');
67+
68+
$extension = new SymfonyContainerResultCacheMetaExtension(
69+
new DefaultParameterMap([]),
70+
new DefaultServiceMap([]),
71+
$this->tmpDir,
72+
$containerXmlPath,
73+
);
74+
75+
self::assertSame('pre-computed-hash', $extension->getHash());
76+
}
77+
1478
/**
1579
* @param list<ContainerContents> $sameHashContents
1680
* @param ContainerContents $invalidatingContent
@@ -30,6 +94,8 @@ public function testContainerHashIsCalculatedCorrectly(
3094
$currentHash = (new SymfonyContainerResultCacheMetaExtension(
3195
$content['parameters'] ?? new DefaultParameterMap([]),
3296
$content['services'] ?? new DefaultServiceMap([]),
97+
__DIR__ . '/../../tmp',
98+
null,
3399
))->getHash();
34100

35101
if ($hash === null) {
@@ -44,6 +110,8 @@ public function testContainerHashIsCalculatedCorrectly(
44110
(new SymfonyContainerResultCacheMetaExtension(
45111
$invalidatingContent['parameters'] ?? new DefaultParameterMap([]),
46112
$invalidatingContent['services'] ?? new DefaultServiceMap([]),
113+
__DIR__ . '/../../tmp',
114+
null,
47115
))->getHash(),
48116
);
49117
}

0 commit comments

Comments
 (0)