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: /** @psalm-mutation-free */
214: public function getModifiers(): int
215: {
216: return $this->betterReflectionProperty->getModifiers();
217: }
218:
219: public function getDeclaringClass(): ReflectionClass
220: {
221: return new ReflectionClass($this->betterReflectionProperty->getImplementingClass());
222: }
223:
224: /**
225: * {@inheritDoc}
226: */
227: #[ReturnTypeWillChange]
228: public function getDocComment()
229: {
230: return $this->betterReflectionProperty->getDocComment() ?? false;
231: }
232:
233: /**
234: * {@inheritDoc}
235: * @codeCoverageIgnore
236: * @infection-ignore-all
237: */
238: public function setAccessible($accessible): void
239: {
240: }
241:
242: public function hasDefaultValue(): bool
243: {
244: return $this->betterReflectionProperty->hasDefaultValue();
245: }
246:
247: /**
248: * @return mixed
249: */
250: #[ReturnTypeWillChange]
251: public function getDefaultValue()
252: {
253: return $this->betterReflectionProperty->getDefaultValue();
254: }
255:
256: public function getDefaultValueExpression(): Expr
257: {
258: return $this->betterReflectionProperty->getDefaultValueExpression();
259: }
260:
261: /**
262: * {@inheritDoc}
263: */
264: #[ReturnTypeWillChange]
265: public function isInitialized($object = null)
266: {
267: try {
268: return $this->betterReflectionProperty->isInitialized($object);
269: } catch (Throwable $e) {
270: throw new CoreReflectionException($e->getMessage(), 0, $e);
271: }
272: }
273:
274: public function isPromoted(): bool
275: {
276: return $this->betterReflectionProperty->isPromoted();
277: }
278:
279: /**
280: * @param class-string|null $name
281: *
282: * @return list<ReflectionAttribute|FakeReflectionAttribute>
283: */
284: public function getAttributes(?string $name = null, int $flags = 0): array
285: {
286: if ($flags !== 0 && $flags !== ReflectionAttribute::IS_INSTANCEOF) {
287: throw new ValueError('Argument #2 ($flags) must be a valid attribute filter flag');
288: }
289:
290: if ($name !== null && $flags !== 0) {
291: $attributes = $this->betterReflectionProperty->getAttributesByInstance($name);
292: } elseif ($name !== null) {
293: $attributes = $this->betterReflectionProperty->getAttributesByName($name);
294: } else {
295: $attributes = $this->betterReflectionProperty->getAttributes();
296: }
297:
298: return array_map(static fn (BetterReflectionAttribute $betterReflectionAttribute) => ReflectionAttributeFactory::create($betterReflectionAttribute), $attributes);
299: }
300:
301: public function isReadOnly(): bool
302: {
303: return $this->betterReflectionProperty->isReadOnly();
304: }
305:
306: /** @psalm-mutation-free */
307: public function isVirtual(): bool
308: {
309: return $this->betterReflectionProperty->isVirtual();
310: }
311:
312: public function hasHooks(): bool
313: {
314: return $this->betterReflectionProperty->hasHooks();
315: }
316:
317: /** @psalm-suppress UndefinedClass */
318: public function hasHook(PropertyHookType $hookType): bool
319: {
320: return $this->betterReflectionProperty->hasHook(BetterReflectionPropertyHookType::fromCoreReflectionPropertyHookType($hookType));
321: }
322:
323: /** @psalm-suppress UndefinedClass */
324: public function getHook(PropertyHookType $hookType): ?\PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod
325: {
326: $hook = $this->betterReflectionProperty->getHook(BetterReflectionPropertyHookType::fromCoreReflectionPropertyHookType($hookType));
327: if ($hook === null) {
328: return null;
329: }
330:
331: return new ReflectionMethod($hook);
332: }
333:
334: /** @return array{get?: ReflectionMethod, set?: ReflectionMethod} */
335: public function getHooks(): array
336: {
337: return array_map(
338: static fn (BetterReflectionMethod $betterReflectionMethod): CoreReflectionMethod => new ReflectionMethod($betterReflectionMethod),
339: $this->betterReflectionProperty->getHooks(),
340: );
341: }
342:
343: /**
344: * @return ReflectionUnionType|ReflectionNamedType|ReflectionIntersectionType|null
345: */
346: public function getSettableType(): ?\ReflectionType
347: {
348: $setHook = $this->betterReflectionProperty->getHook(BetterReflectionPropertyHookType::Set);
349: if ($setHook !== null) {
350: return ReflectionType::fromTypeOrNull($setHook->getParameters()[0]->getType());
351: }
352:
353: if ($this->isVirtual()) {
354: return new ReflectionNamedType('never');
355: }
356:
357: return $this->getType();
358: }
359:
360: /* @return never public function getRawValue(object $object): mixed
361: {
362: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
363: }*/
364: /**
365: * @param mixed $value
366: */
367: public function setRawValue(object $object, $value): void
368: {
369: if ($this->hasHooks()) {
370: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
371: }
372:
373: $this->setValue($object, $value);
374: }
375:
376: /**
377: * @return mixed
378: */
379: public function __get(string $name)
380: {
381: if ($name === 'name') {
382: return $this->betterReflectionProperty->getName();
383: }
384:
385: if ($name === 'class') {
386: return $this->betterReflectionProperty->getImplementingClass()->getName();
387: }
388:
389: throw new OutOfBoundsException(sprintf('Property %s::$%s does not exist.', self::class, $name));
390: }
391:
392: public function getBetterReflection(): BetterReflectionProperty
393: {
394: return $this->betterReflectionProperty;
395: }
396: }
397: