1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace PHPStan\BetterReflection\Reflection\Adapter;
6:
7: use ArgumentCountError;
8: use OutOfBoundsException;
9: use PhpParser\Node\Expr;
10: use PropertyHookType;
11: use ReflectionException as CoreReflectionException;
12: use ReflectionMethod as CoreReflectionMethod;
13: use ReflectionProperty as CoreReflectionProperty;
14: use ReflectionType as CoreReflectionType;
15: use ReturnTypeWillChange;
16: use PHPStan\BetterReflection\Reflection\Exception\ClassDoesNotExist;
17: use PHPStan\BetterReflection\Reflection\Exception\NoObjectProvided;
18: use PHPStan\BetterReflection\Reflection\Exception\NotAnObject;
19: use PHPStan\BetterReflection\Reflection\Exception\ObjectNotInstanceOfClass;
20: use PHPStan\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute;
21: use PHPStan\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod;
22: use PHPStan\BetterReflection\Reflection\ReflectionProperty as BetterReflectionProperty;
23: use PHPStan\BetterReflection\Reflection\ReflectionPropertyHookType as BetterReflectionPropertyHookType;
24: use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound;
25: use Throwable;
26: use TypeError;
27: use ValueError;
28:
29: use function array_map;
30: use function gettype;
31: use function sprintf;
32:
33: /** @psalm-suppress PropertyNotSetInConstructor */
34: final class ReflectionProperty extends CoreReflectionProperty
35: {
36: private BetterReflectionProperty $betterReflectionProperty;
37: /**
38: * @internal
39: *
40: * @see CoreReflectionProperty::IS_FINAL
41: */
42: public const IS_FINAL_COMPATIBILITY = 32;
43:
44: /**
45: * @internal
46: *
47: * @see CoreReflectionProperty::IS_ABSTRACT
48: */
49: public const IS_ABSTRACT_COMPATIBILITY = 64;
50:
51: /**
52: * @internal
53: *
54: * @see CoreReflectionProperty::IS_VIRTUAL
55: */
56: public const IS_VIRTUAL_COMPATIBILITY = 512;
57:
58: /**
59: * @internal
60: *
61: * @see CoreReflectionProperty::IS_PROTECTED_SET
62: */
63: public const IS_PROTECTED_SET_COMPATIBILITY = 2048;
64:
65: /**
66: * @internal
67: *
68: * @see CoreReflectionProperty::IS_PRIVATE_SET
69: */
70: public const IS_PRIVATE_SET_COMPATIBILITY = 4096;
71:
72: /** @internal */
73: public const IS_READONLY_COMPATIBILITY = 128;
74:
75: public function __construct(BetterReflectionProperty $betterReflectionProperty)
76: {
77: $this->betterReflectionProperty = $betterReflectionProperty;
78: unset($this->name);
79: unset($this->class);
80: }
81:
82: /** @return non-empty-string */
83: public function __toString(): string
84: {
85: return $this->betterReflectionProperty->__toString();
86: }
87:
88: public function getName(): string
89: {
90: return $this->betterReflectionProperty->getName();
91: }
92:
93: /**
94: * {@inheritDoc}
95: * @return mixed
96: */
97: #[ReturnTypeWillChange]
98: public function getValue($object = null)
99: {
100: try {
101: return $this->betterReflectionProperty->getValue($object);
102: } catch (NoObjectProvided $exception) {
103: return null;
104: } catch (Throwable $e) {
105: throw new CoreReflectionException($e->getMessage(), 0, $e);
106: }
107: }
108:
109: /** @psalm-suppress MethodSignatureMismatch
110: * @param mixed $objectOrValue
111: * @param mixed $value */
112: public function setValue($objectOrValue, $value = null): void
113: {
114: try {
115: $this->betterReflectionProperty->setValue($objectOrValue, $value);
116: } catch (NoObjectProvided $exception) {
117: throw new ArgumentCountError('ReflectionProperty::setValue() expects exactly 2 arguments, 1 given');
118: } catch (NotAnObject $exception) {
119: throw new TypeError(sprintf('ReflectionProperty::setValue(): Argument #1 ($objectOrValue) must be of type object, %s given', gettype($objectOrValue)));
120: } catch (Throwable $e) {
121: throw new CoreReflectionException($e->getMessage(), 0, $e);
122: }
123: }
124:
125: /**
126: * @param mixed $value
127: */
128: public function setRawValueWithoutLazyInitialization(object $object, $value): void
129: {
130: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
131: }
132:
133: /** @return never */
134: public function isLazy(object $object): bool
135: {
136: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
137: }
138:
139: public function skipLazyInitialization(object $object): void
140: {
141: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
142: }
143:
144: /** @psalm-mutation-free */
145: public function hasType(): bool
146: {
147: return $this->betterReflectionProperty->hasType();
148: }
149:
150: /** @return ReflectionUnionType|ReflectionNamedType|ReflectionIntersectionType|null */
151: public function getType(): ?CoreReflectionType
152: {
153: return ReflectionType::fromTypeOrNull($this->betterReflectionProperty->getType());
154: }
155:
156: public function isPublic(): bool
157: {
158: return $this->betterReflectionProperty->isPublic();
159: }
160:
161: public function isPrivate(): bool
162: {
163: return $this->betterReflectionProperty->isPrivate();
164: }
165:
166: /** @psalm-mutation-free */
167: public function isPrivateSet(): bool
168: {
169: return $this->betterReflectionProperty->isPrivateSet();
170: }
171:
172: /** @psalm-mutation-free */
173: public function isProtected(): bool
174: {
175: return $this->betterReflectionProperty->isProtected();
176: }
177:
178: /** @psalm-mutation-free */
179: public function isProtectedSet(): bool
180: {
181: return $this->betterReflectionProperty->isProtectedSet();
182: }
183:
184: /** @psalm-mutation-free */
185: public function isStatic(): bool
186: {
187: return $this->betterReflectionProperty->isStatic();
188: }
189:
190: /** @psalm-mutation-free */
191: public function isFinal(): bool
192: {
193: return $this->betterReflectionProperty->isFinal();
194: }
195:
196: /** @psalm-mutation-free */
197: public function isAbstract(): bool
198: {
199: return $this->betterReflectionProperty->isAbstract();
200: }
201:
202: /** @psalm-mutation-free */
203: public function isDefault(): bool
204: {
205: return $this->betterReflectionProperty->isDefault();
206: }
207:
208: /** @psalm-mutation-free */
209: public function isDynamic(): bool
210: {
211: return $this->betterReflectionProperty->isDynamic();
212: }
213:
214: public function isReadable(?string $scope, ?object $object = null): bool
215: {
216: try {
217: return $this->betterReflectionProperty->isReadable($scope, $object);
218: } catch (ClassDoesNotExist | IdentifierNotFound | ObjectNotInstanceOfClass $e) {
219: throw new CoreReflectionException($e->getMessage(), 0, $e);
220: }
221: }
222:
223: public function isWritable(?string $scope, ?object $object = null): bool
224: {
225: try {
226: return $this->betterReflectionProperty->isWritable($scope, $object);
227: } catch (ClassDoesNotExist | IdentifierNotFound | ObjectNotInstanceOfClass $e) {
228: throw new CoreReflectionException($e->getMessage(), 0, $e);
229: }
230: }
231:
232: /**
233: * @return int-mask-of<self::IS_*>
234: *
235: * @psalm-mutation-free
236: */
237: public function getModifiers(): int
238: {
239: return $this->betterReflectionProperty->getModifiers();
240: }
241:
242: public function getDeclaringClass(): ReflectionClass
243: {
244: return new ReflectionClass($this->betterReflectionProperty->getImplementingClass());
245: }
246:
247: /**
248: * {@inheritDoc}
249: */
250: #[ReturnTypeWillChange]
251: public function getDocComment()
252: {
253: return $this->betterReflectionProperty->getDocComment() ?? false;
254: }
255:
256: /**
257: * {@inheritDoc}
258: *
259: * @codeCoverageIgnore
260: * @infection-ignore-all
261: */
262: public function setAccessible($accessible): void
263: {
264: }
265:
266: public function hasDefaultValue(): bool
267: {
268: return $this->betterReflectionProperty->hasDefaultValue();
269: }
270:
271: /** @return mixed */
272: #[ReturnTypeWillChange]
273: public function getDefaultValue()
274: {
275: return $this->betterReflectionProperty->getDefaultValue();
276: }
277:
278: public function getDefaultValueExpression(): Expr
279: {
280: return $this->betterReflectionProperty->getDefaultValueExpression();
281: }
282:
283: /**
284: * {@inheritDoc}
285: */
286: #[ReturnTypeWillChange]
287: public function isInitialized($object = null)
288: {
289: try {
290: return $this->betterReflectionProperty->isInitialized($object);
291: } catch (Throwable $e) {
292: throw new CoreReflectionException($e->getMessage(), 0, $e);
293: }
294: }
295:
296: public function isPromoted(): bool
297: {
298: return $this->betterReflectionProperty->isPromoted();
299: }
300:
301: /**
302: * @param class-string|null $name
303: *
304: * @return list<ReflectionAttribute|FakeReflectionAttribute>
305: */
306: public function getAttributes(?string $name = null, int $flags = 0): array
307: {
308: if ($flags !== 0 && $flags !== ReflectionAttribute::IS_INSTANCEOF) {
309: throw new ValueError('Argument #2 ($flags) must be a valid attribute filter flag');
310: }
311:
312: if ($name !== null && $flags !== 0) {
313: $attributes = $this->betterReflectionProperty->getAttributesByInstance($name);
314: } elseif ($name !== null) {
315: $attributes = $this->betterReflectionProperty->getAttributesByName($name);
316: } else {
317: $attributes = $this->betterReflectionProperty->getAttributes();
318: }
319:
320: return array_map(static fn (BetterReflectionAttribute $betterReflectionAttribute) => ReflectionAttributeFactory::create($betterReflectionAttribute), $attributes);
321: }
322:
323: public function isReadOnly(): bool
324: {
325: return $this->betterReflectionProperty->isReadOnly();
326: }
327:
328: /** @psalm-mutation-free */
329: public function isVirtual(): bool
330: {
331: return $this->betterReflectionProperty->isVirtual();
332: }
333:
334: public function hasHooks(): bool
335: {
336: return $this->betterReflectionProperty->hasHooks();
337: }
338:
339: /**
340: * @param \PropertyHookType::* $type
341: */
342: public function hasHook($type): bool
343: {
344: return $this->betterReflectionProperty->hasHook(BetterReflectionPropertyHookType::fromCoreReflectionPropertyHookType($type));
345: }
346:
347: /**
348: * @param \PropertyHookType::* $type
349: */
350: public function getHook($type): ?\PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod
351: {
352: $hook = $this->betterReflectionProperty->getHook(BetterReflectionPropertyHookType::fromCoreReflectionPropertyHookType($type));
353: if ($hook === null) {
354: return null;
355: }
356:
357: return new ReflectionMethod($hook);
358: }
359:
360: /** @return array{get?: ReflectionMethod, set?: ReflectionMethod} */
361: public function getHooks(): array
362: {
363: return array_map(
364: static fn (BetterReflectionMethod $betterReflectionMethod): CoreReflectionMethod => new ReflectionMethod($betterReflectionMethod),
365: $this->betterReflectionProperty->getHooks(),
366: );
367: }
368:
369: /** @return ReflectionUnionType|ReflectionNamedType|ReflectionIntersectionType|null */
370: public function getSettableType(): ?CoreReflectionType
371: {
372: $setHook = $this->betterReflectionProperty->getHook(BetterReflectionPropertyHookType::Set);
373: if ($setHook !== null) {
374: return ReflectionType::fromTypeOrNull($setHook->getParameters()[0]->getType());
375: }
376:
377: if ($this->isVirtual()) {
378: return new ReflectionNamedType('never');
379: }
380:
381: return $this->getType();
382: }
383:
384: /**
385: * @param mixed $value
386: */
387: public function setRawValue(object $object, $value): void
388: {
389: if ($this->hasHooks()) {
390: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
391: }
392:
393: $this->setValue($object, $value);
394: }
395:
396: /**
397: * @return mixed
398: */
399: public function __get(string $name)
400: {
401: if ($name === 'name') {
402: return $this->betterReflectionProperty->getName();
403: }
404:
405: if ($name === 'class') {
406: return $this->betterReflectionProperty->getImplementingClass()->getName();
407: }
408:
409: throw new OutOfBoundsException(sprintf('Property %s::$%s does not exist.', self::class, $name));
410: }
411:
412: public function getMangledName(): string
413: {
414: if ($this->betterReflectionProperty->isPrivate()) {
415: return sprintf(
416: "\0%s\0%s",
417: $this->betterReflectionProperty->getDeclaringClass()->getName(),
418: $this->betterReflectionProperty->getName(),
419: );
420: }
421:
422: if ($this->betterReflectionProperty->isProtected()) {
423: return sprintf(
424: "\0*\0%s",
425: $this->betterReflectionProperty->getName(),
426: );
427: }
428:
429: return $this->betterReflectionProperty->getName();
430: }
431:
432: public function getBetterReflection(): BetterReflectionProperty
433: {
434: return $this->betterReflectionProperty;
435: }
436: }
437: