Skip to content

Commit ea07f8c

Browse files
committed
Remove inefficient caching from PhpMethodReflection
1 parent 9d19cd0 commit ea07f8c

File tree

5 files changed

+121
-81
lines changed

5 files changed

+121
-81
lines changed

conf/config.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,11 @@ services:
302302
tags:
303303
- phpstan.parser.richParserNodeVisitor
304304

305+
-
306+
class: PHPStan\Parser\VariadicMethodsVisitor
307+
tags:
308+
- phpstan.parser.richParserNodeVisitor
309+
305310
-
306311
class: PHPStan\Node\Printer\ExprPrinter
307312

src/Parser/SimpleParser.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ final class SimpleParser implements Parser
1515
public function __construct(
1616
private \PhpParser\Parser $parser,
1717
private NameResolver $nameResolver,
18+
private VariadicMethodsVisitor $variadicMethodsVisitor,
1819
)
1920
{
2021
}
@@ -48,6 +49,7 @@ public function parseString(string $sourceCode): array
4849

4950
$nodeTraverser = new NodeTraverser();
5051
$nodeTraverser->addVisitor($this->nameResolver);
52+
$nodeTraverser->addVisitor($this->variadicMethodsVisitor);
5153

5254
/** @var array<Node\Stmt> */
5355
return $nodeTraverser->traverse($nodes);

src/Parser/VariadicMethodsVisitor.php

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Parser;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Stmt\ClassMethod;
7+
use PhpParser\NodeVisitorAbstract;
8+
use PHPStan\Reflection\ParametersAcceptor;
9+
use function array_key_exists;
10+
11+
final class VariadicMethodsVisitor extends NodeVisitorAbstract
12+
{
13+
14+
private ?Node $topNode = null;
15+
16+
private ?string $inClassLike = null;
17+
18+
private ?string $inNamespace = null;
19+
20+
/** @var array<string, list<string>> */
21+
private array $variadicMethods = [];
22+
23+
public const ATTRIBUTE_NAME = 'variadicMethods';
24+
25+
public function __construct(
26+
private FunctionCallStatementFinder $functionCallStatementFinder,
27+
)
28+
{
29+
}
30+
31+
public function beforeTraverse(array $nodes): ?array
32+
{
33+
$this->topNode = null;
34+
$this->variadicMethods = [];
35+
$this->inClassLike = null;
36+
$this->inNamespace = null;
37+
38+
return null;
39+
}
40+
41+
public function enterNode(Node $node): ?Node
42+
{
43+
if ($this->topNode === null) {
44+
$this->topNode = $node;
45+
}
46+
47+
if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) {
48+
$this->inNamespace = $node->name->toString();
49+
}
50+
51+
if ($node instanceof Node\Stmt\ClassLike && $node->name instanceof Node\Identifier) {
52+
$this->inClassLike = $node->name->name;
53+
}
54+
55+
if ($this->inClassLike !== null && $node instanceof ClassMethod) {
56+
if ($node->getStmts() === null) {
57+
return null; // interface
58+
}
59+
60+
if ($this->functionCallStatementFinder->findFunctionCallInStatements(ParametersAcceptor::VARIADIC_FUNCTIONS, $node->getStmts()) !== null) {
61+
$className = $this->inNamespace !== null ? $this->inNamespace . '\\' . $this->inClassLike : $this->inClassLike;
62+
if (!array_key_exists($className, $this->variadicMethods)) {
63+
$this->variadicMethods[$className] = [];
64+
}
65+
$this->variadicMethods[$className][] = $node->name->name;
66+
}
67+
}
68+
69+
return null;
70+
}
71+
72+
public function leaveNode(Node $node): ?Node
73+
{
74+
if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) {
75+
$this->inNamespace = null;
76+
}
77+
78+
if ($node instanceof Node\Stmt\ClassLike && $node->name instanceof Node\Identifier) {
79+
$this->inClassLike = null;
80+
}
81+
82+
return null;
83+
}
84+
85+
public function afterTraverse(array $nodes): ?array
86+
{
87+
if ($this->topNode !== null && $this->variadicMethods !== []) {
88+
$this->topNode->setAttribute(self::ATTRIBUTE_NAME, $this->variadicMethods);
89+
}
90+
91+
return null;
92+
}
93+
94+
}

src/Reflection/Php/PhpMethodReflection.php

Lines changed: 19 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,10 @@
22

33
namespace PHPStan\Reflection\Php;
44

5-
use PhpParser\Node;
6-
use PhpParser\Node\Stmt\ClassMethod;
7-
use PhpParser\Node\Stmt\Declare_;
8-
use PhpParser\Node\Stmt\Function_;
9-
use PhpParser\Node\Stmt\Namespace_;
105
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod;
116
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter;
12-
use PHPStan\Cache\Cache;
13-
use PHPStan\Parser\FunctionCallStatementFinder;
147
use PHPStan\Parser\Parser;
8+
use PHPStan\Parser\VariadicMethodsVisitor;
159
use PHPStan\Reflection\Assertions;
1610
use PHPStan\Reflection\ClassMemberReflection;
1711
use PHPStan\Reflection\ClassReflection;
@@ -21,7 +15,6 @@
2115
use PHPStan\Reflection\ExtendedParametersAcceptor;
2216
use PHPStan\Reflection\InitializerExprTypeResolver;
2317
use PHPStan\Reflection\MethodPrototypeReflection;
24-
use PHPStan\Reflection\ParametersAcceptor;
2518
use PHPStan\Reflection\ReflectionProvider;
2619
use PHPStan\TrinaryLogic;
2720
use PHPStan\Type\ArrayType;
@@ -36,14 +29,13 @@
3629
use PHPStan\Type\TypehintHelper;
3730
use PHPStan\Type\VoidType;
3831
use ReflectionException;
32+
use function array_key_exists;
3933
use function array_map;
34+
use function count;
4035
use function explode;
41-
use function filemtime;
4236
use function in_array;
43-
use function is_bool;
44-
use function sprintf;
37+
use function is_array;
4538
use function strtolower;
46-
use function time;
4739
use const PHP_VERSION_ID;
4840

4941
/**
@@ -62,6 +54,8 @@ final class PhpMethodReflection implements ExtendedMethodReflection
6254
/** @var list<ExtendedFunctionVariant>|null */
6355
private ?array $variants = null;
6456

57+
private ?bool $containsVariadicCalls = null;
58+
6559
/**
6660
* @param Type[] $phpDocParameterTypes
6761
* @param Type[] $phpDocParameterOutTypes
@@ -75,8 +69,6 @@ public function __construct(
7569
private ReflectionMethod $reflection,
7670
private ReflectionProvider $reflectionProvider,
7771
private Parser $parser,
78-
private FunctionCallStatementFinder $functionCallStatementFinder,
79-
private Cache $cache,
8072
private TemplateTypeMap $templateTypeMap,
8173
private array $phpDocParameterTypes,
8274
private ?Type $phpDocReturnType,
@@ -253,81 +245,27 @@ private function isVariadic(): bool
253245
}
254246

255247
if (!$isNativelyVariadic && $filename !== null) {
256-
$modifiedTime = @filemtime($filename);
257-
if ($modifiedTime === false) {
258-
$modifiedTime = time();
259-
}
260-
$key = sprintf('variadic-method-%s-%s-%s', $declaringClass->getName(), $this->reflection->getName(), $filename);
261-
$variableCacheKey = sprintf('%d-v4', $modifiedTime);
262-
$cachedResult = $this->cache->load($key, $variableCacheKey);
263-
if ($cachedResult === null || !is_bool($cachedResult)) {
264-
$nodes = $this->parser->parseFile($filename);
265-
$result = $this->callsFuncGetArgs($declaringClass, $nodes);
266-
$this->cache->save($key, $variableCacheKey, $result);
267-
return $result;
268-
}
269-
270-
return $cachedResult;
271-
}
272-
273-
return $isNativelyVariadic;
274-
}
275-
276-
/**
277-
* @param Node[] $nodes
278-
*/
279-
private function callsFuncGetArgs(ClassReflection $declaringClass, array $nodes): bool
280-
{
281-
foreach ($nodes as $node) {
282-
if (
283-
$node instanceof Node\Stmt\ClassLike
284-
) {
285-
if (!isset($node->namespacedName)) {
286-
continue;
287-
}
288-
if ($declaringClass->getName() !== (string) $node->namespacedName) {
289-
continue;
290-
}
291-
if ($this->callsFuncGetArgs($declaringClass, $node->stmts)) {
292-
return true;
293-
}
294-
continue;
248+
if ($this->containsVariadicCalls !== null) {
249+
return $this->containsVariadicCalls;
295250
}
296251

297-
if ($node instanceof ClassMethod) {
298-
if ($node->getStmts() === null) {
299-
continue; // interface
300-
}
252+
$nodes = $this->parser->parseFile($filename);
253+
if (count($nodes) > 0) {
254+
$variadicMethods = $nodes[0]->getAttribute(VariadicMethodsVisitor::ATTRIBUTE_NAME);
301255

302-
$methodName = $node->name->name;
303-
if ($methodName === $this->reflection->getName()) {
304-
return $this->functionCallStatementFinder->findFunctionCallInStatements(ParametersAcceptor::VARIADIC_FUNCTIONS, $node->getStmts()) !== null;
256+
if (
257+
is_array($variadicMethods)
258+
&& array_key_exists($declaringClass->getName(), $variadicMethods)
259+
&& in_array($this->reflection->getName(), $variadicMethods[$declaringClass->getName()], true)
260+
) {
261+
return $this->containsVariadicCalls = true;
305262
}
306-
307-
continue;
308263
}
309264

310-
if ($node instanceof Function_) {
311-
continue;
312-
}
313-
314-
if ($node instanceof Namespace_) {
315-
if ($this->callsFuncGetArgs($declaringClass, $node->stmts)) {
316-
return true;
317-
}
318-
continue;
319-
}
320-
321-
if (!$node instanceof Declare_ || $node->stmts === null) {
322-
continue;
323-
}
324-
325-
if ($this->callsFuncGetArgs($declaringClass, $node->stmts)) {
326-
return true;
327-
}
265+
return $this->containsVariadicCalls = false;
328266
}
329267

330-
return false;
268+
return $isNativelyVariadic;
331269
}
332270

333271
public function isPrivate(): bool

tests/PHPStan/Parser/CleaningParserTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public function testParse(
6868
new SimpleParser(
6969
new Php7(new Emulative()),
7070
new NameResolver(),
71+
new VariadicMethodsVisitor(new FunctionCallStatementFinder()),
7172
),
7273
new PhpVersion($phpVersionId),
7374
);

0 commit comments

Comments
 (0)