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: )
48: {
49: }
50:
51: public function getName(): string
52: {
53: return $this->reflection->getName();
54: }
55:
56: public function getDeclaringClass(): ClassReflection
57: {
58: return $this->declaringClass;
59: }
60:
61: public function getDeclaringTrait(): ?ClassReflection
62: {
63: return $this->declaringTrait;
64: }
65:
66: public function getDocComment(): ?string
67: {
68: $docComment = $this->reflection->getDocComment();
69: if ($docComment === false) {
70: return null;
71: }
72:
73: return $docComment;
74: }
75:
76: public function isStatic(): bool
77: {
78: return $this->reflection->isStatic();
79: }
80:
81: public function isPrivate(): bool
82: {
83: return $this->reflection->isPrivate();
84: }
85:
86: public function isPublic(): bool
87: {
88: return $this->reflection->isPublic();
89: }
90:
91: public function isReadOnly(): bool
92: {
93: return $this->reflection->isReadOnly();
94: }
95:
96: public function isReadOnlyByPhpDoc(): bool
97: {
98: return $this->isReadOnlyByPhpDoc;
99: }
100:
101: public function getReadableType(): Type
102: {
103: if ($this->type === null) {
104: $this->type = TypehintHelper::decideTypeFromReflection(
105: $this->nativeType,
106: $this->phpDocType,
107: $this->declaringClass,
108: );
109: }
110:
111: return $this->type;
112: }
113:
114: public function getWritableType(): Type
115: {
116: if ($this->hasHook('set')) {
117: $setHookVariant = $this->getHook('set')->getOnlyVariant();
118: $parameters = $setHookVariant->getParameters();
119: if (isset($parameters[0])) {
120: return $parameters[0]->getType();
121: }
122: }
123:
124: return $this->getReadableType();
125: }
126:
127: public function canChangeTypeAfterAssignment(): bool
128: {
129: if ($this->isStatic()) {
130: return true;
131: }
132:
133: if ($this->isVirtual()->yes()) {
134: return false;
135: }
136:
137: if ($this->hasHook('get')) {
138: return false;
139: }
140:
141: if ($this->hasHook('set')) {
142: return false;
143: }
144:
145: return true;
146: }
147:
148: public function isPromoted(): bool
149: {
150: return $this->reflection->isPromoted();
151: }
152:
153: public function hasPhpDocType(): bool
154: {
155: return $this->phpDocType !== null;
156: }
157:
158: public function getPhpDocType(): Type
159: {
160: if ($this->phpDocType !== null) {
161: return $this->phpDocType;
162: }
163:
164: return new MixedType();
165: }
166:
167: public function hasNativeType(): bool
168: {
169: return $this->nativeType !== null;
170: }
171:
172: public function getNativeType(): Type
173: {
174: if ($this->finalNativeType === null) {
175: $this->finalNativeType = TypehintHelper::decideTypeFromReflection(
176: $this->nativeType,
177: null,
178: $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 isFinal(): TrinaryLogic
246: {
247: return TrinaryLogic::createFromBoolean($this->reflection->isFinal());
248: }
249:
250: public function isVirtual(): TrinaryLogic
251: {
252: return TrinaryLogic::createFromBoolean($this->reflection->isVirtual());
253: }
254:
255: public function hasHook(string $hookType): bool
256: {
257: if ($hookType === 'get') {
258: return $this->getHook !== null;
259: }
260:
261: return $this->setHook !== null;
262: }
263:
264: public function isHooked(): bool
265: {
266: return $this->getHook !== null || $this->setHook !== null;
267: }
268:
269: public function getHook(string $hookType): ExtendedMethodReflection
270: {
271: if ($hookType === 'get') {
272: if ($this->getHook === null) {
273: throw new MissingMethodFromReflectionException($this->declaringClass->getName(), sprintf('$%s::get', $this->reflection->getName()));
274: }
275:
276: return $this->getHook;
277: }
278:
279: if ($this->setHook === null) {
280: throw new MissingMethodFromReflectionException($this->declaringClass->getName(), sprintf('$%s::set', $this->reflection->getName()));
281: }
282:
283: return $this->setHook;
284: }
285:
286: public function isProtectedSet(): bool
287: {
288: return $this->reflection->isProtectedSet();
289: }
290:
291: public function isPrivateSet(): bool
292: {
293: return $this->reflection->isPrivateSet();
294: }
295:
296: public function getAttributes(): array
297: {
298: return $this->attributes;
299: }
300:
301: }
302: