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