Skip to content

Commit e7d1ef6

Browse files
authored
Added extension for internal Reflection classes (fixed #33) (#37)
1 parent 49cb50a commit e7d1ef6

File tree

4 files changed

+166
-0
lines changed

4 files changed

+166
-0
lines changed

extension.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ services:
2424
- exceptionRules.dynamicConstructorThrowTypeExtension
2525
- exceptionRules.dynamicFunctionThrowTypeExtension
2626

27+
-
28+
class: Pepakriz\PHPStanExceptionRules\Extension\ReflectionExtension
29+
tags:
30+
- exceptionRules.dynamicConstructorThrowTypeExtension
31+
2732
-
2833
class: Pepakriz\PHPStanExceptionRules\Rules\ThrowsPhpDocRule
2934
arguments:

src/Extension/ReflectionExtension.php

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Pepakriz\PHPStanExceptionRules\Extension;
4+
5+
use Pepakriz\PHPStanExceptionRules\DynamicConstructorThrowTypeExtension;
6+
use Pepakriz\PHPStanExceptionRules\UnsupportedClassException;
7+
use PhpParser\Node\Expr\New_;
8+
use PhpParser\Node\Name;
9+
use PHPStan\Analyser\Scope;
10+
use PHPStan\Broker\Broker;
11+
use PHPStan\Broker\ClassNotFoundException;
12+
use PHPStan\Reflection\MethodReflection;
13+
use PHPStan\Type\Constant\ConstantStringType;
14+
use PHPStan\Type\ObjectType;
15+
use PHPStan\Type\Type;
16+
use PHPStan\Type\VoidType;
17+
use ReflectionClass;
18+
use ReflectionException;
19+
use ReflectionFunction;
20+
use ReflectionProperty;
21+
use ReflectionZendExtension;
22+
use function is_a;
23+
24+
class ReflectionExtension implements DynamicConstructorThrowTypeExtension
25+
{
26+
27+
/**
28+
* @var Broker
29+
*/
30+
private $broker;
31+
32+
public function __construct(
33+
Broker $broker
34+
)
35+
{
36+
$this->broker = $broker;
37+
}
38+
39+
/**
40+
* @throws UnsupportedClassException
41+
*/
42+
public function getThrowTypeFromConstructor(MethodReflection $methodReflection, New_ $newNode, Scope $scope): Type
43+
{
44+
$className = $methodReflection->getDeclaringClass()->getName();
45+
46+
if (is_a($className, ReflectionClass::class, true)) {
47+
return $this->resolveReflectionClass($newNode, $scope);
48+
}
49+
50+
if (is_a($className, ReflectionProperty::class, true)) {
51+
return $this->resolveReflectionProperty($newNode, $scope);
52+
}
53+
54+
if (is_a($className, ReflectionFunction::class, true)) {
55+
return $this->resolveReflectionFunction($newNode, $scope);
56+
}
57+
58+
if (is_a($className, ReflectionZendExtension::class, true)) {
59+
return $this->resolveReflectionClass($newNode, $scope);
60+
}
61+
62+
throw new UnsupportedClassException();
63+
}
64+
65+
private function resolveReflectionClass(New_ $newNode, Scope $scope): Type
66+
{
67+
$reflectionExceptionType = new ObjectType(ReflectionException::class);
68+
$valueType = $scope->getType($newNode->args[0]->value);
69+
if (!$valueType instanceof ConstantStringType) {
70+
return $reflectionExceptionType;
71+
}
72+
73+
if (!$this->broker->hasClass($valueType->getValue())) {
74+
return $reflectionExceptionType;
75+
}
76+
77+
return new VoidType();
78+
}
79+
80+
private function resolveReflectionFunction(New_ $newNode, Scope $scope): Type
81+
{
82+
$reflectionExceptionType = new ObjectType(ReflectionException::class);
83+
$valueType = $scope->getType($newNode->args[0]->value);
84+
if (!$valueType instanceof ConstantStringType) {
85+
return $reflectionExceptionType;
86+
}
87+
88+
if (!$this->broker->hasFunction(new Name($valueType->getValue()), $scope)) {
89+
return $reflectionExceptionType;
90+
}
91+
92+
return new VoidType();
93+
}
94+
95+
private function resolveReflectionProperty(New_ $newNode, Scope $scope): Type
96+
{
97+
$reflectionExceptionType = new ObjectType(ReflectionException::class);
98+
$valueType = $scope->getType($newNode->args[0]->value);
99+
if (!$valueType instanceof ConstantStringType) {
100+
return $reflectionExceptionType;
101+
}
102+
103+
$propertyType = $scope->getType($newNode->args[1]->value);
104+
if (!$propertyType instanceof ConstantStringType) {
105+
return $reflectionExceptionType;
106+
}
107+
108+
try {
109+
if (!$this->broker->getClass($valueType->getValue())->hasProperty($propertyType->getValue())) {
110+
return $reflectionExceptionType;
111+
}
112+
} catch (ClassNotFoundException $e) {
113+
return $reflectionExceptionType;
114+
}
115+
116+
return new VoidType();
117+
}
118+
119+
}

tests/src/Rules/ThrowsPhpDocRuleTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
use Pepakriz\PHPStanExceptionRules\CheckedExceptionService;
66
use Pepakriz\PHPStanExceptionRules\DynamicThrowTypeService;
7+
use Pepakriz\PHPStanExceptionRules\Extension\ReflectionExtension;
78
use Pepakriz\PHPStanExceptionRules\Rules\Data\CheckedException;
89
use Pepakriz\PHPStanExceptionRules\Rules\DynamicExtension\DynamicExtension;
910
use Pepakriz\PHPStanExceptionRules\RuleTestCase;
1011
use PHPStan\Rules\Rule;
12+
use ReflectionException;
1113
use RuntimeException;
1214

1315
class ThrowsPhpDocRuleTest extends RuleTestCase
@@ -21,11 +23,13 @@ class ThrowsPhpDocRuleTest extends RuleTestCase
2123
protected function getRule(): Rule
2224
{
2325
$dynamicExtension = new DynamicExtension();
26+
$reflectionClassExtension = new ReflectionExtension($this->createBroker());
2427
return new ThrowsPhpDocRule(
2528
new CheckedExceptionService(
2629
[
2730
RuntimeException::class,
2831
CheckedException::class,
32+
ReflectionException::class,
2933
]
3034
),
3135
new DynamicThrowTypeService([
@@ -34,6 +38,7 @@ protected function getRule(): Rule
3438
$dynamicExtension,
3539
], [
3640
$dynamicExtension,
41+
$reflectionClassExtension,
3742
], [
3843
$dynamicExtension,
3944
]),
@@ -98,4 +103,9 @@ public function testAnonymClass(): void
98103
$this->analyse(__DIR__ . '/data/throws-anonym-class.php');
99104
}
100105

106+
public function testPhpInternalFunctions(): void
107+
{
108+
$this->analyse(__DIR__ . '/data/throws-php-internal-functions.php');
109+
}
110+
101111
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Pepakriz\PHPStanExceptionRules\Rules\PhpInternalFunctions;
4+
5+
use ReflectionClass;
6+
use ReflectionFunction;
7+
use ReflectionProperty;
8+
use ReflectionZendExtension;
9+
10+
class Example
11+
{
12+
13+
private $property;
14+
15+
public function testName(): void
16+
{
17+
new ReflectionClass(self::class);
18+
new ReflectionClass('undefinedClass'); // error: Missing @throws ReflectionException annotation
19+
20+
new ReflectionProperty(self::class, 'property');
21+
new ReflectionProperty(self::class, 'undefinedProperty'); // error: Missing @throws ReflectionException annotation
22+
new ReflectionProperty('undefinedClass', 'property'); // error: Missing @throws ReflectionException annotation
23+
new ReflectionProperty('undefinedClass', 'undefinedProperty'); // error: Missing @throws ReflectionException annotation
24+
25+
new ReflectionFunction('count');
26+
new ReflectionFunction('undefinedFunction'); // error: Missing @throws ReflectionException annotation
27+
28+
new ReflectionZendExtension('unknownZendExtension'); // error: Missing @throws ReflectionException annotation
29+
}
30+
31+
}
32+

0 commit comments

Comments
 (0)