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