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