Skip to content

Commit c4a662a

Browse files
committed
Bleeding edge - RuntimeReflectionFunctionRule
1 parent 1d58f26 commit c4a662a

7 files changed

+192
-0
lines changed

conf/bleedingEdge.neon

+1
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ parameters:
1616
checkUnresolvableParameterTypes: true
1717
readOnlyByPhpDoc: true
1818
phpDocParserRequireWhitespaceBeforeDescription: true
19+
runtimeReflectionRules: true

conf/config.level0.neon

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ conditionalTags:
1212
phpstan.rules.rule: %checkUninitializedProperties%
1313
PHPStan\Rules\Methods\ConsistentConstructorRule:
1414
phpstan.rules.rule: %featureToggles.consistentConstructor%
15+
PHPStan\Rules\Api\RuntimeReflectionFunctionRule:
16+
phpstan.rules.rule: %featureToggles.runtimeReflectionRules%
1517

1618
rules:
1719
- PHPStan\Rules\Api\ApiInstantiationRule
@@ -78,6 +80,8 @@ rules:
7880
services:
7981
-
8082
class: PHPStan\Rules\Api\NodeConnectingVisitorAttributesRule
83+
-
84+
class: PHPStan\Rules\Api\RuntimeReflectionFunctionRule
8185
-
8286
class: PHPStan\Rules\Classes\ExistingClassInClassExtendsRule
8387
tags:

conf/config.neon

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ parameters:
4141
checkUnresolvableParameterTypes: false
4242
readOnlyByPhpDoc: false
4343
phpDocParserRequireWhitespaceBeforeDescription: false
44+
runtimeReflectionRules: false
4445
fileExtensions:
4546
- php
4647
checkAdvancedIsset: false
@@ -243,6 +244,7 @@ parametersSchema:
243244
checkUnresolvableParameterTypes: bool()
244245
readOnlyByPhpDoc: bool()
245246
phpDocParserRequireWhitespaceBeforeDescription: bool()
247+
runtimeReflectionRules: bool()
246248
])
247249
fileExtensions: listOf(string())
248250
checkAdvancedIsset: bool()

phpstan-baseline.neon

+10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
parameters:
22
ignoreErrors:
3+
-
4+
message: "#^Function is_a\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#"
5+
count: 1
6+
path: src/Analyser/DirectScopeFactory.php
7+
8+
-
9+
message: "#^Function is_a\\(\\) is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\\. Use objects retrieved from ReflectionProvider instead\\.$#"
10+
count: 1
11+
path: src/Analyser/LazyScopeFactory.php
12+
313
-
414
message: """
515
#^Call to deprecated method getTypeFromValue\\(\\) of class PHPStan\\\\Type\\\\ConstantTypeHelper\\:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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 function array_keys;
11+
use function in_array;
12+
use function sprintf;
13+
use function strpos;
14+
15+
/**
16+
* @implements Rule<Node\Expr\FuncCall>
17+
*/
18+
class RuntimeReflectionFunctionRule implements Rule
19+
{
20+
21+
public function __construct(private ReflectionProvider $reflectionProvider)
22+
{
23+
}
24+
25+
public function getNodeType(): string
26+
{
27+
return Node\Expr\FuncCall::class;
28+
}
29+
30+
public function processNode(Node $node, Scope $scope): array
31+
{
32+
if (!$node->name instanceof Node\Name) {
33+
return [];
34+
}
35+
36+
if (!$this->reflectionProvider->hasFunction($node->name, $scope)) {
37+
return [];
38+
}
39+
40+
$functionReflection = $this->reflectionProvider->getFunction($node->name, $scope);
41+
if (!in_array($functionReflection->getName(), [
42+
'is_a',
43+
'is_subclass_of',
44+
'class_parents',
45+
'class_implements',
46+
'class_uses',
47+
], true)) {
48+
return [];
49+
}
50+
51+
if (!$scope->isInClass()) {
52+
return [];
53+
}
54+
55+
$classReflection = $scope->getClassReflection();
56+
$hasPhpStanInterface = false;
57+
foreach (array_keys($classReflection->getInterfaces()) as $interfaceName) {
58+
if (strpos($interfaceName, 'PHPStan\\') !== 0) {
59+
continue;
60+
}
61+
62+
$hasPhpStanInterface = true;
63+
}
64+
65+
if (!$hasPhpStanInterface) {
66+
return [];
67+
}
68+
69+
return [
70+
RuleErrorBuilder::message(
71+
sprintf('Function %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.', $functionReflection->getName()),
72+
)->build(),
73+
];
74+
}
75+
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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<RuntimeReflectionFunctionRule>
10+
*/
11+
class RuntimeReflectionFunctionRuleTest extends RuleTestCase
12+
{
13+
14+
protected function getRule(): Rule
15+
{
16+
return new RuntimeReflectionFunctionRule($this->createReflectionProvider());
17+
}
18+
19+
public function testRule(): void
20+
{
21+
$this->analyse([__DIR__ . '/data/runtime-reflection-function.php'], [
22+
[
23+
'Function is_a() 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+
'Function is_subclass_of() 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+
46,
29+
],
30+
[
31+
'Function class_parents() 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+
49,
33+
],
34+
[
35+
'Function class_implements() 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+
50,
37+
],
38+
[
39+
'Function class_uses() 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+
51,
41+
],
42+
]);
43+
}
44+
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace RuntimeReflectionFunction;
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+
if (is_a($o, \stdClass::class)) {
17+
18+
}
19+
}
20+
21+
}
22+
23+
class Bar implements DynamicMethodReturnTypeExtension
24+
{
25+
26+
public function getClass(): string
27+
{
28+
// TODO: Implement getClass() method.
29+
}
30+
31+
public function isMethodSupported(MethodReflection $methodReflection): bool
32+
{
33+
// TODO: Implement isMethodSupported() method.
34+
}
35+
36+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
37+
{
38+
// TODO: Implement getTypeFromMethodCall() method.
39+
}
40+
41+
public function doFoo(object $o): void
42+
{
43+
if (is_a($o, \stdClass::class)) {
44+
45+
}
46+
if (is_subclass_of($o, \stdClass::class)) {
47+
48+
}
49+
$p = class_parents($o);
50+
$i = class_implements($o);
51+
$t = class_uses($o);
52+
}
53+
54+
}

0 commit comments

Comments
 (0)