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: * @api
22: */
23: final class PhpPropertyReflection implements ExtendedPropertyReflection
24: {
25:
26: private ?Type $finalNativeType = null;
27:
28: private ?Type $type = null;
29:
30: /**
31: * @param list<AttributeReflection> $attributes
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: if ($this->type === null) {
105: $this->type = TypehintHelper::decideTypeFromReflection(
106: $this->nativeType,
107: $this->phpDocType,
108: $this->declaringClass,
109: );
110: }
111:
112: return $this->type;
113: }
114:
115: public function getWritableType(): Type
116: {
117: if ($this->hasHook('set')) {
118: $setHookVariant = $this->getHook('set')->getOnlyVariant();
119: $parameters = $setHookVariant->getParameters();
120: if (isset($parameters[0])) {
121: return $parameters[0]->getType();
122: }
123: }
124:
125: return $this->getReadableType();
126: }
127:
128: public function canChangeTypeAfterAssignment(): bool
129: {
130: if ($this->isStatic()) {
131: return true;
132: }
133:
134: if ($this->isVirtual()->yes()) {
135: return false;
136: }
137:
138: if ($this->hasHook('get')) {
139: return false;
140: }
141:
142: if ($this->hasHook('set')) {
143: return false;
144: }
145:
146: return true;
147: }
148:
149: public function isPromoted(): bool
150: {
151: return $this->reflection->isPromoted();
152: }
153:
154: public function hasPhpDocType(): bool
155: {
156: return $this->phpDocType !== null;
157: }
158:
159: public function getPhpDocType(): Type
160: {
161: if ($this->phpDocType !== null) {
162: return $this->phpDocType;
163: }
164:
165: return new MixedType();
166: }
167:
168: public function hasNativeType(): bool
169: {
170: return $this->nativeType !== null;
171: }
172:
173: public function getNativeType(): Type
174: {
175: if ($this->finalNativeType === null) {
176: $this->finalNativeType = TypehintHelper::decideTypeFromReflection(
177: $this->nativeType,
178: selfClass: $this->declaringClass,
179: );
180: }
181:
182: return $this->finalNativeType;
183: }
184:
185: public function isReadable(): bool
186: {
187: if ($this->isStatic()) {
188: return true;
189: }
190:
191: if (!$this->isVirtual()->yes()) {
192: return true;
193: }
194:
195: return $this->hasHook('get');
196: }
197:
198: public function isWritable(): bool
199: {
200: if ($this->isStatic()) {
201: return true;
202: }
203:
204: if (!$this->isVirtual()->yes()) {
205: return true;
206: }
207:
208: return $this->hasHook('set');
209: }
210:
211: public function getDeprecatedDescription(): ?string
212: {
213: if ($this->isDeprecated) {
214: return $this->deprecatedDescription;
215: }
216:
217: return null;
218: }
219:
220: public function isDeprecated(): TrinaryLogic
221: {
222: return TrinaryLogic::createFromBoolean($this->isDeprecated);
223: }
224:
225: public function isInternal(): TrinaryLogic
226: {
227: return TrinaryLogic::createFromBoolean($this->isInternal);
228: }
229:
230: public function isAllowedPrivateMutation(): bool
231: {
232: return $this->isAllowedPrivateMutation;
233: }
234:
235: public function getNativeReflection(): ReflectionProperty
236: {
237: return $this->reflection;
238: }
239:
240: public function isAbstract(): TrinaryLogic
241: {
242: return TrinaryLogic::createFromBoolean($this->reflection->isAbstract());
243: }
244:
245: public function isFinalByKeyword(): TrinaryLogic
246: {
247: return TrinaryLogic::createFromBoolean($this->reflection->isFinal());
248: }
249:
250: public function isFinal(): TrinaryLogic
251: {
252: return TrinaryLogic::createFromBoolean($this->isFinal);
253: }
254:
255: public function isVirtual(): TrinaryLogic
256: {
257: return TrinaryLogic::createFromBoolean($this->reflection->isVirtual());
258: }
259:
260: public function hasHook(string $hookType): bool
261: {
262: if ($hookType === 'get') {
263: return $this->getHook !== null;
264: }
265:
266: return $this->setHook !== null;
267: }
268:
269: public function isHooked(): bool
270: {
271: return $this->getHook !== null || $this->setHook !== null;
272: }
273:
274: public function getHook(string $hookType): ExtendedMethodReflection
275: {
276: if ($hookType === 'get') {
277: if ($this->getHook === null) {
278: throw new MissingMethodFromReflectionException($this->declaringClass->getName(), sprintf('$%s::get', $this->reflection->getName()));
279: }
280:
281: return $this->getHook;
282: }
283:
284: if ($this->setHook === null) {
285: throw new MissingMethodFromReflectionException($this->declaringClass->getName(), sprintf('$%s::set', $this->reflection->getName()));
286: }
287:
288: return $this->setHook;
289: }
290:
291: public function isProtectedSet(): bool
292: {
293: return $this->reflection->isProtectedSet();
294: }
295:
296: public function isPrivateSet(): bool
297: {
298: return $this->reflection->isPrivateSet();
299: }
300:
301: public function getAttributes(): array
302: {
303: return $this->attributes;
304: }
305:
306: }
307: