1: | <?php declare(strict_types = 1); |
2: | |
3: | namespace PHPStan\Reflection\Php; |
4: | |
5: | use PHPStan\BetterReflection\Reflection\Adapter\ReflectionIntersectionType; |
6: | use PHPStan\BetterReflection\Reflection\Adapter\ReflectionNamedType; |
7: | use PHPStan\BetterReflection\Reflection\Adapter\ReflectionProperty; |
8: | use PHPStan\BetterReflection\Reflection\Adapter\ReflectionUnionType; |
9: | use PHPStan\Reflection\AttributeReflection; |
10: | use PHPStan\Reflection\ClassReflection; |
11: | use PHPStan\Reflection\ExtendedMethodReflection; |
12: | use PHPStan\Reflection\ExtendedPropertyReflection; |
13: | use PHPStan\Reflection\MissingMethodFromReflectionException; |
14: | use PHPStan\TrinaryLogic; |
15: | use PHPStan\Type\MixedType; |
16: | use PHPStan\Type\Type; |
17: | use PHPStan\Type\TypehintHelper; |
18: | use function sprintf; |
19: | |
20: | |
21: | |
22: | |
23: | final class PhpPropertyReflection implements ExtendedPropertyReflection |
24: | { |
25: | |
26: | private ?Type $finalNativeType = null; |
27: | |
28: | private ?Type $type = null; |
29: | |
30: | |
31: | |
32: | |
33: | public function __construct( |
34: | private ClassReflection $declaringClass, |
35: | private ?ClassReflection $declaringTrait, |
36: | private ReflectionUnionType|ReflectionNamedType|ReflectionIntersectionType|null $nativeType, |
37: | private ?Type $phpDocType, |
38: | private ReflectionProperty $reflection, |
39: | private ?ExtendedMethodReflection $getHook, |
40: | private ?ExtendedMethodReflection $setHook, |
41: | private ?string $deprecatedDescription, |
42: | private bool $isDeprecated, |
43: | private bool $isInternal, |
44: | private bool $isReadOnlyByPhpDoc, |
45: | private bool $isAllowedPrivateMutation, |
46: | private array $attributes, |
47: | private bool $isFinal, |
48: | ) |
49: | { |
50: | } |
51: | |
52: | public function getName(): string |
53: | { |
54: | return $this->reflection->getName(); |
55: | } |
56: | |
57: | public function getDeclaringClass(): ClassReflection |
58: | { |
59: | return $this->declaringClass; |
60: | } |
61: | |
62: | public function getDeclaringTrait(): ?ClassReflection |
63: | { |
64: | return $this->declaringTrait; |
65: | } |
66: | |
67: | public function getDocComment(): ?string |
68: | { |
69: | $docComment = $this->reflection->getDocComment(); |
70: | if ($docComment === false) { |
71: | return null; |
72: | } |
73: | |
74: | return $docComment; |
75: | } |
76: | |
77: | public function isStatic(): bool |
78: | { |
79: | return $this->reflection->isStatic(); |
80: | } |
81: | |
82: | public function isPrivate(): bool |
83: | { |
84: | return $this->reflection->isPrivate(); |
85: | } |
86: | |
87: | public function isPublic(): bool |
88: | { |
89: | return $this->reflection->isPublic(); |
90: | } |
91: | |
92: | public function isReadOnly(): bool |
93: | { |
94: | return $this->reflection->isReadOnly(); |
95: | } |
96: | |
97: | public function isReadOnlyByPhpDoc(): bool |
98: | { |
99: | return $this->isReadOnlyByPhpDoc; |
100: | } |
101: | |
102: | public function getReadableType(): Type |
103: | { |
104: | return $this->type ??= TypehintHelper::decideTypeFromReflection( |
105: | $this->nativeType, |
106: | $this->phpDocType, |
107: | $this->declaringClass, |
108: | ); |
109: | } |
110: | |
111: | public function getWritableType(): Type |
112: | { |
113: | if ($this->hasHook('set')) { |
114: | $setHookVariant = $this->getHook('set')->getOnlyVariant(); |
115: | $parameters = $setHookVariant->getParameters(); |
116: | if (isset($parameters[0])) { |
117: | return $parameters[0]->getType(); |
118: | } |
119: | } |
120: | |
121: | return $this->getReadableType(); |
122: | } |
123: | |
124: | public function canChangeTypeAfterAssignment(): bool |
125: | { |
126: | if ($this->isStatic()) { |
127: | return true; |
128: | } |
129: | |
130: | if ($this->isVirtual()->yes()) { |
131: | return false; |
132: | } |
133: | |
134: | if ($this->hasHook('get')) { |
135: | return false; |
136: | } |
137: | |
138: | if ($this->hasHook('set')) { |
139: | return false; |
140: | } |
141: | |
142: | return true; |
143: | } |
144: | |
145: | public function isPromoted(): bool |
146: | { |
147: | return $this->reflection->isPromoted(); |
148: | } |
149: | |
150: | public function hasPhpDocType(): bool |
151: | { |
152: | return $this->phpDocType !== null; |
153: | } |
154: | |
155: | public function getPhpDocType(): Type |
156: | { |
157: | if ($this->phpDocType !== null) { |
158: | return $this->phpDocType; |
159: | } |
160: | |
161: | return new MixedType(); |
162: | } |
163: | |
164: | public function hasNativeType(): bool |
165: | { |
166: | return $this->nativeType !== null; |
167: | } |
168: | |
169: | public function getNativeType(): Type |
170: | { |
171: | return $this->finalNativeType ??= TypehintHelper::decideTypeFromReflection( |
172: | $this->nativeType, |
173: | selfClass: $this->declaringClass, |
174: | ); |
175: | } |
176: | |
177: | public function isReadable(): bool |
178: | { |
179: | if ($this->isStatic()) { |
180: | return true; |
181: | } |
182: | |
183: | if (!$this->isVirtual()->yes()) { |
184: | return true; |
185: | } |
186: | |
187: | return $this->hasHook('get'); |
188: | } |
189: | |
190: | public function isWritable(): bool |
191: | { |
192: | if ($this->isStatic()) { |
193: | return true; |
194: | } |
195: | |
196: | if (!$this->isVirtual()->yes()) { |
197: | return true; |
198: | } |
199: | |
200: | return $this->hasHook('set'); |
201: | } |
202: | |
203: | public function getDeprecatedDescription(): ?string |
204: | { |
205: | if ($this->isDeprecated) { |
206: | return $this->deprecatedDescription; |
207: | } |
208: | |
209: | return null; |
210: | } |
211: | |
212: | public function isDeprecated(): TrinaryLogic |
213: | { |
214: | return TrinaryLogic::createFromBoolean($this->isDeprecated); |
215: | } |
216: | |
217: | public function isInternal(): TrinaryLogic |
218: | { |
219: | return TrinaryLogic::createFromBoolean($this->isInternal); |
220: | } |
221: | |
222: | public function isAllowedPrivateMutation(): bool |
223: | { |
224: | return $this->isAllowedPrivateMutation; |
225: | } |
226: | |
227: | public function getNativeReflection(): ReflectionProperty |
228: | { |
229: | return $this->reflection; |
230: | } |
231: | |
232: | public function isAbstract(): TrinaryLogic |
233: | { |
234: | return TrinaryLogic::createFromBoolean($this->reflection->isAbstract()); |
235: | } |
236: | |
237: | public function isFinalByKeyword(): TrinaryLogic |
238: | { |
239: | return TrinaryLogic::createFromBoolean($this->reflection->isFinal()); |
240: | } |
241: | |
242: | public function isFinal(): TrinaryLogic |
243: | { |
244: | return TrinaryLogic::createFromBoolean($this->isFinal); |
245: | } |
246: | |
247: | public function isVirtual(): TrinaryLogic |
248: | { |
249: | return TrinaryLogic::createFromBoolean($this->reflection->isVirtual()); |
250: | } |
251: | |
252: | public function hasHook(string $hookType): bool |
253: | { |
254: | if ($hookType === 'get') { |
255: | return $this->getHook !== null; |
256: | } |
257: | |
258: | return $this->setHook !== null; |
259: | } |
260: | |
261: | public function isHooked(): bool |
262: | { |
263: | return $this->getHook !== null || $this->setHook !== null; |
264: | } |
265: | |
266: | public function getHook(string $hookType): ExtendedMethodReflection |
267: | { |
268: | if ($hookType === 'get') { |
269: | if ($this->getHook === null) { |
270: | throw new MissingMethodFromReflectionException($this->declaringClass->getName(), sprintf('$%s::get', $this->reflection->getName())); |
271: | } |
272: | |
273: | return $this->getHook; |
274: | } |
275: | |
276: | if ($this->setHook === null) { |
277: | throw new MissingMethodFromReflectionException($this->declaringClass->getName(), sprintf('$%s::set', $this->reflection->getName())); |
278: | } |
279: | |
280: | return $this->setHook; |
281: | } |
282: | |
283: | public function isProtectedSet(): bool |
284: | { |
285: | return $this->reflection->isProtectedSet(); |
286: | } |
287: | |
288: | public function isPrivateSet(): bool |
289: | { |
290: | return $this->reflection->isPrivateSet(); |
291: | } |
292: | |
293: | public function getAttributes(): array |
294: | { |
295: | return $this->attributes; |
296: | } |
297: | |
298: | } |
299: | |