Skip to content

Commit b66210f

Browse files
committed
Cache OptimizedDirectorySourceLocator for faster subsequent runs and less work needed by child processes
1 parent 4e37a2d commit b66210f

File tree

2 files changed

+184
-159
lines changed

2 files changed

+184
-159
lines changed

src/Reflection/BetterReflection/SourceLocator/OptimizedDirectorySourceLocator.php

Lines changed: 6 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -9,48 +9,28 @@
99
use PHPStan\BetterReflection\Reflector\Reflector;
1010
use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection;
1111
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
12-
use PHPStan\Php\PhpVersion;
1312
use PHPStan\Reflection\ConstantNameHelper;
1413
use PHPStan\ShouldNotHappenException;
1514
use function array_key_exists;
1615
use function array_values;
17-
use function count;
1816
use function current;
19-
use function ltrim;
20-
use function php_strip_whitespace;
21-
use function preg_match_all;
22-
use function preg_replace;
23-
use function sprintf;
2417
use function strtolower;
2518

2619
class OptimizedDirectorySourceLocator implements SourceLocator
2720
{
2821

29-
private PhpFileCleaner $cleaner;
30-
31-
private string $extraTypes;
32-
33-
/** @var array<string, string>|null */
34-
private ?array $classToFile = null;
35-
36-
/** @var array<string, string>|null */
37-
private ?array $constantToFile = null;
38-
39-
/** @var array<string, array<int, string>>|null */
40-
private ?array $functionToFiles = null;
41-
4222
/**
43-
* @param string[] $files
23+
* @param array<string, string> $classToFile
24+
* @param array<string, array<int, string>> $functionToFiles
25+
* @param array<string, string> $constantToFile
4426
*/
4527
public function __construct(
4628
private FileNodesFetcher $fileNodesFetcher,
47-
private PhpVersion $phpVersion,
48-
private array $files,
29+
private array $classToFile,
30+
private array $functionToFiles,
31+
private array $constantToFile,
4932
)
5033
{
51-
$this->extraTypes = $this->phpVersion->supportsEnums() ? '|enum' : '';
52-
53-
$this->cleaner = new PhpFileCleaner();
5434
}
5535

5636
public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection
@@ -141,13 +121,6 @@ private function nodeToReflection(Reflector $reflector, FetchedNode $fetchedNode
141121

142122
private function findFileByClass(string $className): ?string
143123
{
144-
if ($this->classToFile === null) {
145-
$this->init();
146-
if ($this->classToFile === null) {
147-
throw new ShouldNotHappenException();
148-
}
149-
}
150-
151124
if (!array_key_exists($className, $this->classToFile)) {
152125
return null;
153126
}
@@ -157,13 +130,6 @@ private function findFileByClass(string $className): ?string
157130

158131
private function findFileByConstant(string $constantName): ?string
159132
{
160-
if ($this->constantToFile === null) {
161-
$this->init();
162-
if ($this->constantToFile === null) {
163-
throw new ShouldNotHappenException();
164-
}
165-
}
166-
167133
if (!array_key_exists($constantName, $this->constantToFile)) {
168134
return null;
169135
}
@@ -176,132 +142,18 @@ private function findFileByConstant(string $constantName): ?string
176142
*/
177143
private function findFilesByFunction(string $functionName): array
178144
{
179-
if ($this->functionToFiles === null) {
180-
$this->init();
181-
if ($this->functionToFiles === null) {
182-
throw new ShouldNotHappenException();
183-
}
184-
}
185-
186145
if (!array_key_exists($functionName, $this->functionToFiles)) {
187146
return [];
188147
}
189148

190149
return $this->functionToFiles[$functionName];
191150
}
192151

193-
private function init(): void
194-
{
195-
$classToFile = [];
196-
$constantToFile = [];
197-
$functionToFiles = [];
198-
foreach ($this->files as $file) {
199-
$symbols = $this->findSymbols($file);
200-
foreach ($symbols['classes'] as $classInFile) {
201-
$classToFile[$classInFile] = $file;
202-
}
203-
foreach ($symbols['constants'] as $constantInFile) {
204-
$constantToFile[$constantInFile] = $file;
205-
}
206-
foreach ($symbols['functions'] as $functionInFile) {
207-
if (!array_key_exists($functionInFile, $functionToFiles)) {
208-
$functionToFiles[$functionInFile] = [];
209-
}
210-
$functionToFiles[$functionInFile][] = $file;
211-
}
212-
}
213-
214-
$this->classToFile = $classToFile;
215-
$this->functionToFiles = $functionToFiles;
216-
$this->constantToFile = $constantToFile;
217-
}
218-
219-
/**
220-
* Inspired by Composer\Autoload\ClassMapGenerator::findClasses()
221-
* @link https://github.com/composer/composer/blob/45d3e133a4691eccb12e9cd6f9dfd76eddc1906d/src/Composer/Autoload/ClassMapGenerator.php#L216
222-
*
223-
* @return array{classes: string[], functions: string[], constants: string[]}
224-
*/
225-
private function findSymbols(string $file): array
226-
{
227-
$contents = @php_strip_whitespace($file);
228-
if ($contents === '') {
229-
return ['classes' => [], 'functions' => [], 'constants' => []];
230-
}
231-
232-
$matchResults = (bool) preg_match_all(sprintf('{\b(?:(?:class|interface|trait|const|function%s)\s)|(?:define\s*\()}i', $this->extraTypes), $contents, $matches);
233-
if (!$matchResults) {
234-
return ['classes' => [], 'functions' => [], 'constants' => []];
235-
}
236-
237-
$contents = $this->cleaner->clean($contents, count($matches[0]));
238-
239-
preg_match_all(sprintf('{
240-
(?:
241-
\b(?<![\$:>])(?:
242-
(?: (?P<type>class|interface|trait%s) \s++ (?P<name>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\-]*+) )
243-
| (?: (?P<function>function) \s++ (?:&\s*)? (?P<fname>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\-]*+) \s*+ [&\(] )
244-
| (?: (?P<constant>const) \s++ (?P<cname>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\-]*+) \s*+ [^;] )
245-
| (?: (?:\\\)? (?P<define>define) \s*+ \( \s*+ [\'"] (?P<dname>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:[\\\\]{1,2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+) )
246-
| (?: (?P<ns>namespace) (?P<nsname>\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] )
247-
)
248-
)
249-
}ix', $this->extraTypes), $contents, $matches);
250-
251-
$classes = [];
252-
$functions = [];
253-
$constants = [];
254-
$namespace = '';
255-
256-
for ($i = 0, $len = count($matches['type']); $i < $len; $i++) {
257-
if (isset($matches['ns'][$i]) && $matches['ns'][$i] !== '') {
258-
$namespace = preg_replace('~\s+~', '', strtolower($matches['nsname'][$i])) . '\\';
259-
continue;
260-
}
261-
262-
if ($matches['function'][$i] !== '') {
263-
$functions[] = strtolower(ltrim($namespace . $matches['fname'][$i], '\\'));
264-
continue;
265-
}
266-
267-
if ($matches['constant'][$i] !== '') {
268-
$constants[] = ConstantNameHelper::normalize(ltrim($namespace . $matches['cname'][$i], '\\'));
269-
}
270-
271-
if ($matches['define'][$i] !== '') {
272-
$constants[] = ConstantNameHelper::normalize($matches['dname'][$i]);
273-
continue;
274-
}
275-
276-
$name = $matches['name'][$i];
277-
278-
// skip anon classes extending/implementing
279-
if ($name === 'extends' || $name === 'implements') {
280-
continue;
281-
}
282-
283-
$classes[] = strtolower(ltrim($namespace . $name, '\\'));
284-
}
285-
286-
return [
287-
'classes' => $classes,
288-
'functions' => $functions,
289-
'constants' => $constants,
290-
];
291-
}
292-
293152
/**
294153
* @return list<Reflection>
295154
*/
296155
public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array
297156
{
298-
if ($this->classToFile === null || $this->functionToFiles === null || $this->constantToFile === null) {
299-
$this->init();
300-
if ($this->classToFile === null || $this->functionToFiles === null || $this->constantToFile === null) {
301-
throw new ShouldNotHappenException();
302-
}
303-
}
304-
305157
$reflections = [];
306158
if ($identifierType->isClass()) {
307159
foreach ($this->classToFile as $file) {

0 commit comments

Comments
 (0)