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