Skip to content

Commit 5363066

Browse files
committed
Bleeding edge - RuntimeReflectionInstantiationRule
1 parent 99ddeaf commit 5363066

5 files changed

+262
-0
lines changed

conf/config.level0.neon

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ conditionalTags:
1414
phpstan.rules.rule: %featureToggles.consistentConstructor%
1515
PHPStan\Rules\Api\RuntimeReflectionFunctionRule:
1616
phpstan.rules.rule: %featureToggles.runtimeReflectionRules%
17+
PHPStan\Rules\Api\RuntimeReflectionInstantiationRule:
18+
phpstan.rules.rule: %featureToggles.runtimeReflectionRules%
1719

1820
rules:
1921
- PHPStan\Rules\Api\ApiInstantiationRule
@@ -82,6 +84,8 @@ services:
8284
class: PHPStan\Rules\Api\NodeConnectingVisitorAttributesRule
8385
-
8486
class: PHPStan\Rules\Api\RuntimeReflectionFunctionRule
87+
-
88+
class: PHPStan\Rules\Api\RuntimeReflectionInstantiationRule
8589
-
8690
class: PHPStan\Rules\Classes\ExistingClassInClassExtendsRule
8791
tags:

phpstan-baseline.neon

+25
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,16 @@ parameters:
169169
count: 1
170170
path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php
171171

172+
-
173+
message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#"
174+
count: 1
175+
path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php
176+
177+
-
178+
message: "#^Creating new ReflectionFunction is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#"
179+
count: 1
180+
path: src/Reflection/BetterReflection/SourceLocator/AutoloadSourceLocator.php
181+
172182
-
173183
message: "#^Parameter \\#2 \\$node of method PHPStan\\\\BetterReflection\\\\SourceLocator\\\\Ast\\\\Strategy\\\\NodeToReflection\\:\\:__invoke\\(\\) expects PhpParser\\\\Node\\\\Expr\\\\ArrowFunction\\|PhpParser\\\\Node\\\\Expr\\\\Closure\\|PhpParser\\\\Node\\\\Expr\\\\FuncCall\\|PhpParser\\\\Node\\\\Stmt\\\\Class_\\|PhpParser\\\\Node\\\\Stmt\\\\Const_\\|PhpParser\\\\Node\\\\Stmt\\\\Enum_\\|PhpParser\\\\Node\\\\Stmt\\\\Function_\\|PhpParser\\\\Node\\\\Stmt\\\\Interface_\\|PhpParser\\\\Node\\\\Stmt\\\\Trait_, PhpParser\\\\Node\\\\Stmt\\\\ClassLike given\\.$#"
174184
count: 1
@@ -189,6 +199,21 @@ parameters:
189199
count: 1
190200
path: src/Reflection/BetterReflection/SourceLocator/OptimizedSingleFileSourceLocator.php
191201

202+
-
203+
message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#"
204+
count: 1
205+
path: src/Reflection/BetterReflection/SourceLocator/ReflectionClassSourceLocator.php
206+
207+
-
208+
message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#"
209+
count: 1
210+
path: src/Reflection/BetterReflection/SourceLocator/RewriteClassAliasSourceLocator.php
211+
212+
-
213+
message: "#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#"
214+
count: 1
215+
path: src/Reflection/BetterReflection/SourceLocator/SkipClassAliasSourceLocator.php
216+
192217
-
193218
message: "#^Method PHPStan\\\\Reflection\\\\ClassReflection\\:\\:getCacheKey\\(\\) should return string but returns string\\|null\\.$#"
194219
count: 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Api;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\ReflectionProvider;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Rules\RuleErrorBuilder;
10+
use ReflectionClass;
11+
use ReflectionClassConstant;
12+
use ReflectionEnum;
13+
use ReflectionEnumBackedCase;
14+
use ReflectionExtension;
15+
use ReflectionFiber;
16+
use ReflectionFunction;
17+
use ReflectionGenerator;
18+
use ReflectionMethod;
19+
use ReflectionObject;
20+
use ReflectionParameter;
21+
use ReflectionProperty;
22+
use ReflectionZendExtension;
23+
use function array_keys;
24+
use function in_array;
25+
use function sprintf;
26+
use function strpos;
27+
28+
/**
29+
* @implements Rule<Node\Expr\New_>
30+
*/
31+
class RuntimeReflectionInstantiationRule implements Rule
32+
{
33+
34+
public function __construct(private ReflectionProvider $reflectionProvider)
35+
{
36+
}
37+
38+
public function getNodeType(): string
39+
{
40+
return Node\Expr\New_::class;
41+
}
42+
43+
public function processNode(Node $node, Scope $scope): array
44+
{
45+
if (!$node->class instanceof Node\Name) {
46+
return [];
47+
}
48+
49+
$className = $scope->resolveName($node->class);
50+
if (!$this->reflectionProvider->hasClass($className)) {
51+
return [];
52+
}
53+
54+
$classReflection = $this->reflectionProvider->getClass($className);
55+
if (!in_array($classReflection->getName(), [
56+
ReflectionMethod::class,
57+
ReflectionClass::class,
58+
ReflectionClassConstant::class,
59+
ReflectionEnum::class,
60+
ReflectionEnumBackedCase::class,
61+
ReflectionZendExtension::class,
62+
ReflectionExtension::class,
63+
ReflectionFunction::class,
64+
ReflectionObject::class,
65+
ReflectionParameter::class,
66+
ReflectionProperty::class,
67+
ReflectionGenerator::class,
68+
ReflectionFiber::class,
69+
], true)) {
70+
return [];
71+
}
72+
73+
if (!$scope->isInClass()) {
74+
return [];
75+
}
76+
77+
$scopeClassReflection = $scope->getClassReflection();
78+
$hasPhpStanInterface = false;
79+
foreach (array_keys($scopeClassReflection->getInterfaces()) as $interfaceName) {
80+
if (strpos($interfaceName, 'PHPStan\\') !== 0) {
81+
continue;
82+
}
83+
84+
$hasPhpStanInterface = true;
85+
}
86+
87+
if (!$hasPhpStanInterface) {
88+
return [];
89+
}
90+
91+
return [
92+
RuleErrorBuilder::message(
93+
sprintf('Creating new %s is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.', $classReflection->getName()),
94+
)->build(),
95+
];
96+
}
97+
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Api;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
8+
/**
9+
* @extends RuleTestCase<RuntimeReflectionInstantiationRule>
10+
*/
11+
class RuntimeReflectionInstantiationRuleTest extends RuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new RuntimeReflectionInstantiationRule($this->createReflectionProvider());
17+
}
18+
19+
public function testRule(): void
20+
{
21+
$this->analyse([__DIR__ . '/data/runtime-reflection-instantiation.php'], [
22+
[
23+
'Creating new ReflectionMethod is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
24+
43,
25+
],
26+
[
27+
'Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
28+
44,
29+
],
30+
[
31+
'Creating new ReflectionClassConstant is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
32+
45,
33+
],
34+
[
35+
'Creating new ReflectionEnum is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
36+
46,
37+
],
38+
[
39+
'Creating new ReflectionEnumBackedCase is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
40+
47,
41+
],
42+
[
43+
'Creating new ReflectionZendExtension is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
44+
48,
45+
],
46+
[
47+
'Creating new ReflectionExtension is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
48+
49,
49+
],
50+
[
51+
'Creating new ReflectionFunction is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
52+
50,
53+
],
54+
[
55+
'Creating new ReflectionObject is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
56+
51,
57+
],
58+
[
59+
'Creating new ReflectionParameter is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
60+
52,
61+
],
62+
[
63+
'Creating new ReflectionProperty is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
64+
53,
65+
],
66+
[
67+
'Creating new ReflectionGenerator is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
68+
54,
69+
],
70+
[
71+
'Creating new ReflectionFiber is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.',
72+
55,
73+
],
74+
]);
75+
}
76+
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace RuntimeReflectionInstantiation;
4+
5+
use PhpParser\Node\Expr\MethodCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\MethodReflection;
8+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
9+
use PHPStan\Type\Type;
10+
11+
class Foo
12+
{
13+
14+
public function doFoo(object $o): void
15+
{
16+
new \stdClass();
17+
new \ReflectionMethod($o, 'foo');
18+
}
19+
20+
}
21+
22+
class Bar implements DynamicMethodReturnTypeExtension
23+
{
24+
25+
public function getClass(): string
26+
{
27+
// TODO: Implement getClass() method.
28+
}
29+
30+
public function isMethodSupported(MethodReflection $methodReflection): bool
31+
{
32+
// TODO: Implement isMethodSupported() method.
33+
}
34+
35+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
36+
{
37+
// TODO: Implement getTypeFromMethodCall() method.
38+
}
39+
40+
public function doFoo(object $o, \Fiber $f, \Generator $g): void
41+
{
42+
new \stdClass();
43+
new \ReflectionMethod($o, 'foo');
44+
new \ReflectionClass(\stdClass::class);
45+
new \ReflectionClassConstant(\stdClass::class, 'foo');
46+
new \ReflectionEnum(\stdClass::class);
47+
new \ReflectionEnumBackedCase(\stdClass::class, 'foo');
48+
new \ReflectionZendExtension('foo');
49+
new \ReflectionExtension('foo');
50+
new \ReflectionFunction('foo');
51+
new \ReflectionObject($o);
52+
new \ReflectionParameter('foo', 'foo');
53+
new \ReflectionProperty(\stdClass::class, 'foo');
54+
new \ReflectionGenerator($g);
55+
new \ReflectionFiber($f);
56+
}
57+
58+
}

0 commit comments

Comments
 (0)