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: /**
322: * @param \PropertyHookType::* $type
323: */
324: public function hasHook($type): bool
325: {
326: return $this->betterReflectionProperty->hasHook(BetterReflectionPropertyHookType::fromCoreReflectionPropertyHookType($type));
327: }
328:
329: /**
330: * @param \PropertyHookType::* $type
331: */
332: public function getHook($type): ?\PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod
333: {
334: $hook = $this->betterReflectionProperty->getHook(BetterReflectionPropertyHookType::fromCoreReflectionPropertyHookType($type));
335: if ($hook === null) {
336: return null;
337: }
338:
339: return new ReflectionMethod($hook);
340: }
341:
342: /** @return array{get?: ReflectionMethod, set?: ReflectionMethod} */
343: public function getHooks(): array
344: {
345: return array_map(
346: static fn (BetterReflectionMethod $betterReflectionMethod): CoreReflectionMethod => new ReflectionMethod($betterReflectionMethod),
347: $this->betterReflectionProperty->getHooks(),
348: );
349: }
350:
351: /**
352: * @return ReflectionUnionType|ReflectionNamedType|ReflectionIntersectionType|null
353: */
354: public function getSettableType(): ?\ReflectionType
355: {
356: $setHook = $this->betterReflectionProperty->getHook(BetterReflectionPropertyHookType::Set);
357: if ($setHook !== null) {
358: return ReflectionType::fromTypeOrNull($setHook->getParameters()[0]->getType());
359: }
360:
361: if ($this->isVirtual()) {
362: return new ReflectionNamedType('never');
363: }
364:
365: return $this->getType();
366: }
367:
368: /* @return never public function getRawValue(object $object): mixed
369: {
370: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
371: }*/
372: /**
373: * @param mixed $value
374: */
375: public function setRawValue(object $object, $value): void
376: {
377: if ($this->hasHooks()) {
378: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
379: }
380:
381: $this->setValue($object, $value);
382: }
383:
384: /**
385: * @return mixed
386: */
387: public function __get(string $name)
388: {
389: if ($name === 'name') {
390: return $this->betterReflectionProperty->getName();
391: }
392:
393: if ($name === 'class') {
394: return $this->betterReflectionProperty->getImplementingClass()->getName();
395: }
396:
397: throw new OutOfBoundsException(sprintf('Property %s::$%s does not exist.', self::class, $name));
398: }
399:
400: public function getMangledName(): string
401: {
402: if ($this->betterReflectionProperty->isPrivate()) {
403: return sprintf(
404: "\0%s\0%s",
405: $this->betterReflectionProperty->getDeclaringClass()->getName(),
406: $this->betterReflectionProperty->getName(),
407: );
408: }
409:
410: if ($this->betterReflectionProperty->isProtected()) {
411: return sprintf(
412: "\0*\0%s",
413: $this->betterReflectionProperty->getName(),
414: );
415: }
416:
417: return $this->betterReflectionProperty->getName();
418: }
419:
420: public function getBetterReflection(): BetterReflectionProperty
421: {
422: return $this->betterReflectionProperty;
423: }
424: }
425: