Skip to content

Commit f79efb8

Browse files
committed
Reflection cache serializing data to disk
1 parent 9ebb467 commit f79efb8

File tree

9 files changed

+181
-30
lines changed

9 files changed

+181
-30
lines changed

src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php

Lines changed: 105 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,22 @@
77
use PHPStan\BetterReflection\Identifier\Identifier;
88
use PHPStan\BetterReflection\Identifier\IdentifierType;
99
use PHPStan\BetterReflection\Reflection\Reflection;
10+
use PHPStan\BetterReflection\Reflection\ReflectionClass;
11+
use PHPStan\BetterReflection\Reflection\ReflectionConstant;
12+
use PHPStan\BetterReflection\Reflection\ReflectionEnum;
13+
use PHPStan\BetterReflection\Reflection\ReflectionFunction;
1014
use PHPStan\BetterReflection\Reflector\Reflector;
1115
use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection;
1216
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
17+
use PHPStan\Cache\Cache;
18+
use PHPStan\File\CouldNotReadFileException;
1319
use PHPStan\Reflection\ConstantNameHelper;
1420
use PHPStan\ShouldNotHappenException;
1521
use function array_key_exists;
1622
use function array_values;
1723
use function current;
24+
use function hash_file;
25+
use function sprintf;
1826
use function strtolower;
1927

2028
final class OptimizedDirectorySourceLocator implements SourceLocator
@@ -27,80 +35,148 @@ final class OptimizedDirectorySourceLocator implements SourceLocator
2735
*/
2836
public function __construct(
2937
private FileNodesFetcher $fileNodesFetcher,
38+
private Cache $cache,
3039
private array $classToFile,
3140
private array $functionToFiles,
3241
private array $constantToFile,
3342
)
3443
{
3544
}
3645

46+
/**
47+
* @return array{non-empty-string, string}
48+
*/
49+
private function getCacheKeys(string $file, Identifier $identifier): array
50+
{
51+
$fileHash = hash_file('sha256', $file);
52+
if ($fileHash === false) {
53+
throw new CouldNotReadFileException($file);
54+
}
55+
56+
$reflectionCacheKey = sprintf('odsl-%s-%s-%s', $file, $identifier->getType()->getName(), $identifier->getName());
57+
$variableCacheKey = sprintf('v1-%s', $fileHash);
58+
59+
return [$reflectionCacheKey, $variableCacheKey];
60+
}
61+
3762
#[Override]
3863
public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection
3964
{
4065
if ($identifier->isClass()) {
41-
$className = strtolower($identifier->getName());
42-
$file = $this->findFileByClass($className);
66+
$identifierName = strtolower($identifier->getName());
67+
$file = $this->findFileByClass($identifierName);
4368
if ($file === null) {
4469
return null;
4570
}
71+
$files = [$file];
72+
} elseif ($identifier->isFunction()) {
73+
$identifierName = strtolower($identifier->getName());
74+
$files = $this->findFilesByFunction($identifierName);
75+
} elseif ($identifier->isConstant()) {
76+
$identifierName = ConstantNameHelper::normalize($identifier->getName());
77+
$file = $this->findFileByConstant($identifierName);
4678

47-
$fetchedClassNodes = $this->fileNodesFetcher->fetchNodes($file)->getClassNodes();
48-
49-
if (!array_key_exists($className, $fetchedClassNodes)) {
79+
if ($file === null) {
5080
return null;
5181
}
5282

53-
/** @var FetchedNode<Node\Stmt\ClassLike> $fetchedClassNode */
54-
$fetchedClassNode = current($fetchedClassNodes[$className]);
83+
$files = [$file];
84+
} else {
85+
return null;
86+
}
5587

56-
return $this->nodeToReflection($reflector, $fetchedClassNode);
88+
foreach ($files as $file) {
89+
[$reflectionCacheKey, $variableCacheKey] = $this->getCacheKeys($file, $identifier);
90+
$cachedReflection = $this->cache->load($reflectionCacheKey, $variableCacheKey);
91+
if ($cachedReflection === null) {
92+
continue;
93+
}
94+
95+
if ($identifier->isConstant()) {
96+
return ReflectionConstant::importFromCache($reflector, $cachedReflection);
97+
}
98+
if ($identifier->isFunction()) {
99+
return ReflectionFunction::importFromCache($reflector, $cachedReflection);
100+
}
101+
if ($identifier->isClass()) {
102+
if (array_key_exists('backingType', $cachedReflection)) {
103+
return ReflectionEnum::importFromCache($reflector, $cachedReflection);
104+
}
105+
106+
return ReflectionClass::importFromCache($reflector, $cachedReflection);
107+
}
57108
}
58109

59-
if ($identifier->isFunction()) {
60-
$functionName = strtolower($identifier->getName());
61-
$files = $this->findFilesByFunction($functionName);
110+
if ($identifier->isClass()) {
111+
$fetchedClassNode = null;
112+
foreach ($files as $file) {
113+
$fetchedClassNodes = $this->fileNodesFetcher->fetchNodes($file)->getClassNodes();
114+
115+
if (!array_key_exists($identifierName, $fetchedClassNodes)) {
116+
return null;
117+
}
118+
119+
/** @var FetchedNode<Node\Stmt\ClassLike> $fetchedClassNode */
120+
$fetchedClassNode = current($fetchedClassNodes[$identifierName]);
121+
}
122+
123+
if ($fetchedClassNode === null) {
124+
return null;
125+
}
126+
127+
[$reflectionCacheKey, $variableCacheKey] = $this->getCacheKeys($file, $identifier); // @phpstan-ignore variable.undefined
128+
$classReflection = $this->nodeToReflection($reflector, $fetchedClassNode);
129+
$this->cache->save($reflectionCacheKey, $variableCacheKey, $classReflection->exportToCache());
62130

131+
return $classReflection;
132+
} elseif ($identifier->isFunction()) {
63133
$fetchedFunctionNode = null;
64134
foreach ($files as $file) {
65135
$fetchedFunctionNodes = $this->fileNodesFetcher->fetchNodes($file)->getFunctionNodes();
66136

67-
if (!array_key_exists($functionName, $fetchedFunctionNodes)) {
137+
if (!array_key_exists($identifierName, $fetchedFunctionNodes)) {
68138
continue;
69139
}
70140

71141
/** @var FetchedNode<Node\Stmt\Function_> $fetchedFunctionNode */
72-
$fetchedFunctionNode = current($fetchedFunctionNodes[$functionName]);
142+
$fetchedFunctionNode = current($fetchedFunctionNodes[$identifierName]);
73143
}
74144

75145
if ($fetchedFunctionNode === null) {
76146
return null;
77147
}
78148

79-
return $this->nodeToReflection($reflector, $fetchedFunctionNode);
80-
}
149+
[$reflectionCacheKey, $variableCacheKey] = $this->getCacheKeys($file, $identifier); // @phpstan-ignore variable.undefined
150+
$functionReflection = $this->nodeToReflection($reflector, $fetchedFunctionNode);
151+
$this->cache->save($reflectionCacheKey, $variableCacheKey, $functionReflection->exportToCache());
81152

82-
if ($identifier->isConstant()) {
83-
$constantName = ConstantNameHelper::normalize($identifier->getName());
84-
$file = $this->findFileByConstant($constantName);
153+
return $functionReflection;
154+
} elseif ($identifier->isConstant()) {
155+
$fetchedConstantNode = null;
156+
foreach ($files as $file) {
157+
$fetchedConstantNodes = $this->fileNodesFetcher->fetchNodes($file)->getConstantNodes();
85158

86-
if ($file === null) {
87-
return null;
88-
}
159+
if (!array_key_exists($identifierName, $fetchedConstantNodes)) {
160+
return null;
161+
}
89162

90-
$fetchedConstantNodes = $this->fileNodesFetcher->fetchNodes($file)->getConstantNodes();
163+
/** @var FetchedNode<Node\Stmt\Const_|Node\Expr\FuncCall> $fetchedConstantNode */
164+
$fetchedConstantNode = current($fetchedConstantNodes[$identifierName]);
165+
}
91166

92-
if (!array_key_exists($constantName, $fetchedConstantNodes)) {
167+
if ($fetchedConstantNode === null) {
93168
return null;
94169
}
95170

96-
/** @var FetchedNode<Node\Stmt\Const_|Node\Expr\FuncCall> $fetchedConstantNode */
97-
$fetchedConstantNode = current($fetchedConstantNodes[$constantName]);
98-
99-
return $this->nodeToReflection(
171+
[$reflectionCacheKey, $variableCacheKey] = $this->getCacheKeys($file, $identifier); // @phpstan-ignore variable.undefined
172+
$constantReflection = $this->nodeToReflection(
100173
$reflector,
101174
$fetchedConstantNode,
102-
$this->findConstantPositionInConstNode($fetchedConstantNode->getNode(), $constantName),
175+
$this->findConstantPositionInConstNode($fetchedConstantNode->getNode(), $identifierName),
103176
);
177+
$this->cache->save($reflectionCacheKey, $variableCacheKey, $constantReflection->exportToCache());
178+
179+
return $constantReflection;
104180
}
105181

106182
return null;
@@ -109,7 +185,7 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
109185
/**
110186
* @param FetchedNode<Node\Stmt\ClassLike>|FetchedNode<Node\Stmt\Function_>|FetchedNode<Node\Stmt\Const_|Node\Expr\FuncCall> $fetchedNode
111187
*/
112-
private function nodeToReflection(Reflector $reflector, FetchedNode $fetchedNode, ?int $positionInNode = null): Reflection
188+
private function nodeToReflection(Reflector $reflector, FetchedNode $fetchedNode, ?int $positionInNode = null): ReflectionClass|ReflectionConstant|ReflectionFunction
113189
{
114190
$nodeToReflection = new NodeToReflection();
115191
return $nodeToReflection->__invoke(

src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ private function createCachedDirectorySourceLocator(array $fileHashes, string $c
8989

9090
return new OptimizedDirectorySourceLocator(
9191
$this->fileNodesFetcher,
92+
$this->cache,
9293
$classToFile,
9394
$functionToFiles,
9495
$constantToFile,

src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocator.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,20 @@
99
use PHPStan\BetterReflection\Reflection\Reflection;
1010
use PHPStan\BetterReflection\Reflection\ReflectionClass;
1111
use PHPStan\BetterReflection\Reflection\ReflectionConstant;
12+
use PHPStan\BetterReflection\Reflection\ReflectionEnum;
1213
use PHPStan\BetterReflection\Reflection\ReflectionFunction;
1314
use PHPStan\BetterReflection\Reflector\Reflector;
1415
use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection;
1516
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
17+
use PHPStan\Cache\Cache;
1618
use PHPStan\DependencyInjection\GenerateFactory;
19+
use PHPStan\File\CouldNotReadFileException;
1720
use PHPStan\Reflection\ConstantNameHelper;
1821
use PHPStan\ShouldNotHappenException;
1922
use function array_key_exists;
2023
use function array_keys;
24+
use function hash_file;
25+
use function sprintf;
2126
use function strtolower;
2227

2328
#[GenerateFactory(interface: OptimizedSingleFileSourceLocatorFactory::class)]
@@ -29,14 +34,29 @@ final class OptimizedSingleFileSourceLocator implements SourceLocator
2934

3035
public function __construct(
3136
private FileNodesFetcher $fileNodesFetcher,
37+
private Cache $cache,
3238
private string $fileName,
3339
)
3440
{
3541
}
3642

43+
private function getVariableCacheKey(string $file): string
44+
{
45+
$fileHash = hash_file('sha256', $file);
46+
if ($fileHash === false) {
47+
throw new CouldNotReadFileException($file);
48+
}
49+
return sprintf('v1-%s', $fileHash);
50+
}
51+
3752
#[Override]
3853
public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection
3954
{
55+
$presentSymbolsCacheKey = sprintf('osfsl-%s-presentSymbols', $this->fileName);
56+
if ($this->presentSymbols === null) {
57+
$variableCacheKey = $this->getVariableCacheKey($this->fileName);
58+
$this->presentSymbols = $this->cache->load($presentSymbolsCacheKey, $variableCacheKey);
59+
}
4060
if ($this->presentSymbols !== null) {
4161
if ($identifier->isClass()) {
4262
$className = strtolower($identifier->getName());
@@ -57,6 +77,26 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
5777
}
5878
}
5979
}
80+
81+
$reflectionCacheKey = sprintf('osfsl-%s-%s-%s', $this->fileName, $identifier->getType()->getName(), $identifier->getName());
82+
$variableCacheKey = $this->getVariableCacheKey($this->fileName);
83+
$cachedReflection = $this->cache->load($reflectionCacheKey, $variableCacheKey);
84+
if ($cachedReflection !== null) {
85+
if ($identifier->isConstant()) {
86+
return ReflectionConstant::importFromCache($reflector, $cachedReflection);
87+
}
88+
if ($identifier->isFunction()) {
89+
return ReflectionFunction::importFromCache($reflector, $cachedReflection);
90+
}
91+
if ($identifier->isClass()) {
92+
if (array_key_exists('backingType', $cachedReflection)) {
93+
return ReflectionEnum::importFromCache($reflector, $cachedReflection);
94+
}
95+
96+
return ReflectionClass::importFromCache($reflector, $cachedReflection);
97+
}
98+
}
99+
60100
$fetchedNodesResult = $this->fileNodesFetcher->fetchNodes($this->fileName);
61101
if ($this->presentSymbols === null) {
62102
$presentSymbols = [
@@ -75,7 +115,9 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
75115
}
76116

77117
$this->presentSymbols = $presentSymbols;
118+
$this->cache->save($presentSymbolsCacheKey, $variableCacheKey, $presentSymbols);
78119
}
120+
79121
$nodeToReflection = new NodeToReflection();
80122
if ($identifier->isClass()) {
81123
$classNodes = $fetchedNodesResult->getClassNodes();
@@ -95,6 +137,8 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
95137
throw new ShouldNotHappenException();
96138
}
97139

140+
$this->cache->save($reflectionCacheKey, $variableCacheKey, $classReflection->exportToCache());
141+
98142
return $classReflection;
99143
}
100144
}
@@ -117,6 +161,8 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
117161
throw new ShouldNotHappenException();
118162
}
119163

164+
$this->cache->save($reflectionCacheKey, $variableCacheKey, $functionReflection->exportToCache());
165+
120166
return $functionReflection;
121167
}
122168
}
@@ -162,6 +208,8 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
162208
throw new ShouldNotHappenException();
163209
}
164210

211+
$this->cache->save($reflectionCacheKey, $variableCacheKey, $constantReflection->exportToCache());
212+
165213
return $constantReflection;
166214
}
167215

tests/PHPStan/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocatorTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\Type\Constant\ConstantIntegerType;
1111
use TestSingleFileSourceLocator\AFoo;
1212
use TestSingleFileSourceLocator\InCondition;
13+
use function array_merge;
1314
use function class_alias;
1415

1516
function testFunctionForLocator(): void // phpcs:disable
@@ -78,4 +79,14 @@ class_alias(AFoo::class, 'A_Foo');
7879
$this->assertSame(AFoo::class, $class->getName());
7980
}
8081

82+
public static function getAdditionalConfigFiles(): array
83+
{
84+
return array_merge(
85+
parent::getAdditionalConfigFiles(),
86+
[
87+
__DIR__ . '/disableCache.neon',
88+
],
89+
);
90+
}
91+
8192
}

tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ public static function getAdditionalConfigFiles(): array
323323
return array_merge(
324324
parent::getAdditionalConfigFiles(),
325325
[
326-
__DIR__ . '/OptimizedDirectorySourceLocatorTest.neon',
326+
__DIR__ . '/disableCache.neon',
327327
],
328328
);
329329
}

tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocatorTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use SingleFileSourceLocatorTestClass;
1515
use TestSingleFileSourceLocator\AFoo;
1616
use function array_map;
17+
use function array_merge;
1718
use const PHP_VERSION_ID;
1819

1920
class OptimizedSingleFileSourceLocatorTest extends PHPStanTestCase
@@ -258,4 +259,14 @@ public function testLocateIdentifiersByType(
258259
$this->assertEqualsCanonicalizing($expectedIdentifiers, $actualIdentifiers);
259260
}
260261

262+
public static function getAdditionalConfigFiles(): array
263+
{
264+
return array_merge(
265+
parent::getAdditionalConfigFiles(),
266+
[
267+
__DIR__ . '/disableCache.neon',
268+
],
269+
);
270+
}
271+
261272
}

tests/PHPStan/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocatorTest.neon renamed to tests/PHPStan/Reflection/BetterReflection/SourceLocator/disableCache.neon

File renamed without changes.

0 commit comments

Comments
 (0)