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: * @api
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: