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: 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: