-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathQuantAccessorMethodReflectionExtension.php
More file actions
175 lines (141 loc) · 5.23 KB
/
Copy pathQuantAccessorMethodReflectionExtension.php
File metadata and controls
175 lines (141 loc) · 5.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
<?php
/**
* This file is part of the quant project.
*
* (c) 2023 Thorsten Suckow-Homberg <[email protected]>
*
* For full copyright and license information, please consult the LICENSE-file distributed
* with this source code.
*/
declare(strict_types=1);
namespace Quant\PHPStan\Reflection;
use PHPStan\BetterReflection\Reflection\ReflectionNamedType;
use ReflectionType;
use PHPStan\BetterReflection\Reflection\ReflectionUnionType;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\MethodsClassReflectionExtension;
use PHPStan\ShouldNotHappenException;
use Quant\Core\Attribute\Getter;
use Quant\Core\Attribute\Setter;
use Quant\Core\Lang\Modifier;
use Quant\Core\Trait\AccessorTrait;
use ReflectionClass;
use ReflectionException;
/**
* Extension for resolving to classes where #[Setter] / #[Getter] is either used
* - on class level
* - on class property level
* - on constructor parameter level (constructor property promotion)
*
* Owning classes must use a AccessorTrait, or inherit from a class that uses an AccessorTrait, in order for
* getters/setters to be properly resolved-.
*
*/
class QuantAccessorMethodReflectionExtension implements MethodsClassReflectionExtension
{
public function hasMethod(ClassReflection $classReflection, string $methodName): bool
{
$usesTrait = $this->doesClassUseAccessorTrait($classReflection);
if (!$usesTrait) {
return false;
}
return !!$this->queryMethod($classReflection, $methodName);
}
/**
* @throws ShouldNotHappenException
*/
public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection
{
$methodReflection = $this->queryMethod($classReflection, $methodName);
if (!$methodReflection) {
throw new ShouldNotHappenException();
}
return $methodReflection;
}
protected function queryMethod(ClassReflection $declaringClass, string $methodName): ?MethodReflection
{
$methodReflection = null;
while ($declaringClass) {
if (($methodReflection = $this->resolveMethod($declaringClass, $methodName))) {
break;
}
$declaringClass = $declaringClass->getParentClass();
}
return $methodReflection;
}
public function resolveMethod(ClassReflection $classReflection, string $methodName): ?MethodReflection
{
$cfg = $this->getPropertyConfig($classReflection, $methodName);
if (!$cfg) {
return null;
}
return new AccessorMethodReflection($classReflection, $methodName, ...$cfg);
}
/**
* @throws ReflectionException
*
* @return null|array{isSetter: bool, propertyType: ReflectionType|null, modifier: Modifier}
*/
protected function getPropertyConfig(ClassReflection $classReflection, string $methodName): ?array
{
$reflectionClass = new ReflectionClass($classReflection->getName());
$propName = lcfirst(substr($methodName, 3));
$prefix = "get";
switch (true) {
case (str_starts_with($methodName, "set")):
$prefix = "set";
// no break intentional
case (str_starts_with($methodName, "get")):
break;
case (str_starts_with($methodName, "is")):
$prefix = "is";
$propName = lcfirst(substr($methodName, 2));
break;
default:
$propName = null;
break;
}
if (!$propName) {
return null;
}
$classAttributes = $reflectionClass->getAttributes($prefix === "set" ? Setter::class : Getter::class);
if ($reflectionClass->hasProperty($propName)) {
$reflectionProperty = $reflectionClass->getProperty($propName);
/* @phpstan-ignore-next-line */
if ($prefix === "is" && $reflectionProperty->getType()?->getName() !== "bool") {
return null;
}
$attributes = $reflectionProperty->getAttributes($prefix === "set" ? Setter::class : Getter::class);
if (empty($attributes)) {
$attributes = $classAttributes;
}
if (empty($attributes)) {
return null;
}
$modArgs = $attributes[0]->getArguments();
$modifier = $modArgs ?
($modArgs[0] instanceof Modifier ? $modArgs[0] : Modifier::PUBLIC) : Modifier::PUBLIC;
return [
"isSetter" => !($prefix === "get" || $prefix === "is"),
"propertyType" => $reflectionProperty->getType(),
"modifier" => $modifier
];
}
return null;
}
private function doesClassUseAccessorTrait(ClassReflection $classReflection): bool
{
$parent = $classReflection->getNativeReflection();
$isTraitUsed = false;
while ($parent) {
$traits = $parent->getTraitNames();
if (in_array(AccessorTrait::class, $traits)) {
$isTraitUsed = true;
break;
}
$parent = $parent->getParentClass();
}
return $isTraitUsed;
}
}