1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace PHPStan\BetterReflection\Reflection\Adapter;
6:
7: use OutOfBoundsException;
8: use ReflectionClass as CoreReflectionClass;
9: use ReflectionException as CoreReflectionException;
10: use ReflectionExtension as CoreReflectionExtension;
11: use ReflectionObject as CoreReflectionObject;
12: use ReturnTypeWillChange;
13: use PHPStan\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute;
14: use PHPStan\BetterReflection\Reflection\ReflectionClass as BetterReflectionClass;
15: use PHPStan\BetterReflection\Reflection\ReflectionClassConstant as BetterReflectionClassConstant;
16: use PHPStan\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod;
17: use PHPStan\BetterReflection\Reflection\ReflectionObject as BetterReflectionObject;
18: use PHPStan\BetterReflection\Reflection\ReflectionProperty as BetterReflectionProperty;
19: use PHPStan\BetterReflection\Util\FileHelper;
20: use ValueError;
21:
22: use function array_combine;
23: use function array_map;
24: use function array_values;
25: use function func_num_args;
26: use function sprintf;
27: use function strtolower;
28:
29: /** @psalm-suppress PropertyNotSetInConstructor */
30: final class ReflectionObject extends CoreReflectionObject
31: {
32: private BetterReflectionObject $betterReflectionObject;
33: public function __construct(BetterReflectionObject $betterReflectionObject)
34: {
35: $this->betterReflectionObject = $betterReflectionObject;
36: unset($this->name);
37: }
38:
39: /** @return non-empty-string */
40: public function __toString(): string
41: {
42: return $this->betterReflectionObject->__toString();
43: }
44:
45: public function getName(): string
46: {
47: return $this->betterReflectionObject->getName();
48: }
49:
50: public function getBetterReflection(): BetterReflectionObject
51: {
52: return $this->betterReflectionObject;
53: }
54:
55: public function isInternal(): bool
56: {
57: return $this->betterReflectionObject->isInternal();
58: }
59:
60: public function isUserDefined(): bool
61: {
62: return $this->betterReflectionObject->isUserDefined();
63: }
64:
65: public function isInstantiable(): bool
66: {
67: return $this->betterReflectionObject->isInstantiable();
68: }
69:
70: public function isCloneable(): bool
71: {
72: return $this->betterReflectionObject->isCloneable();
73: }
74:
75: /**
76: * {@inheritDoc}
77: * @return non-empty-string|false
78: */
79: #[ReturnTypeWillChange]
80: public function getFileName()
81: {
82: $fileName = $this->betterReflectionObject->getFileName();
83:
84: return $fileName !== null ? FileHelper::normalizeSystemPath($fileName) : false;
85: }
86:
87: /**
88: * {@inheritDoc}
89: * @psalm-mutation-free
90: */
91: #[ReturnTypeWillChange]
92: public function getStartLine()
93: {
94: return $this->betterReflectionObject->getStartLine();
95: }
96:
97: /**
98: * {@inheritDoc}
99: * @psalm-mutation-free
100: */
101: #[ReturnTypeWillChange]
102: public function getEndLine()
103: {
104: return $this->betterReflectionObject->getEndLine();
105: }
106:
107: /**
108: * {@inheritDoc}
109: */
110: #[ReturnTypeWillChange]
111: public function getDocComment()
112: {
113: return $this->betterReflectionObject->getDocComment() ?? false;
114: }
115:
116: public function getConstructor(): ?\PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod
117: {
118: $constructor = $this->betterReflectionObject->getConstructor();
119:
120: if ($constructor === null) {
121: return null;
122: }
123:
124: return new ReflectionMethod($constructor);
125: }
126:
127: /**
128: * {@inheritDoc}
129: */
130: public function hasMethod($name): bool
131: {
132: if ($name === '') {
133: return false;
134: }
135:
136: return $this->betterReflectionObject->hasMethod($this->getMethodRealName($name));
137: }
138:
139: /**
140: * {@inheritDoc}
141: */
142: public function getMethod($name): ReflectionMethod
143: {
144: $method = $name !== '' ? $this->betterReflectionObject->getMethod($this->getMethodRealName($name)) : null;
145:
146: if ($method === null) {
147: throw new CoreReflectionException(sprintf('Method %s::%s() does not exist', $this->betterReflectionObject->getName(), $name));
148: }
149:
150: return new ReflectionMethod($method);
151: }
152:
153: /**
154: * @param non-empty-string $name
155: *
156: * @return non-empty-string
157: */
158: private function getMethodRealName(string $name): string
159: {
160: $realMethodNames = array_map(static fn (BetterReflectionMethod $method): string => $method->getName(), $this->betterReflectionObject->getMethods());
161:
162: $methodNames = array_combine(array_map(static fn (string $methodName): string => strtolower($methodName), $realMethodNames), $realMethodNames);
163:
164: $lowercasedName = strtolower($name);
165:
166: return $methodNames[$lowercasedName] ?? $name;
167: }
168:
169: /**
170: * @param int-mask-of<ReflectionMethod::IS_*>|null $filter
171: * @return list<ReflectionMethod>
172: */
173: public function getMethods($filter = null): array
174: {
175: return array_values(array_map(
176: static fn (BetterReflectionMethod $method): ReflectionMethod => new ReflectionMethod($method),
177: $this->betterReflectionObject->getMethods($filter ?? 0),
178: ));
179: }
180:
181: /**
182: * {@inheritDoc}
183: */
184: public function hasProperty($name): bool
185: {
186: if ($name === '') {
187: return false;
188: }
189:
190: return $this->betterReflectionObject->hasProperty($name);
191: }
192:
193: /**
194: * @param string $name
195: * @return ReflectionProperty
196: */
197: public function getProperty($name): \ReflectionProperty
198: {
199: $property = $name !== '' ? $this->betterReflectionObject->getProperty($name) : null;
200:
201: if ($property === null) {
202: throw new CoreReflectionException(sprintf('Property %s::$%s does not exist', $this->betterReflectionObject->getName(), $name));
203: }
204:
205: return new ReflectionProperty($property);
206: }
207:
208: /**
209: * @param int-mask-of<ReflectionProperty::IS_*>|null $filter
210: * @return list<ReflectionProperty>
211: */
212: public function getProperties($filter = null): array
213: {
214: return array_values(array_map(
215: static fn (BetterReflectionProperty $property): ReflectionProperty => new ReflectionProperty($property),
216: $this->betterReflectionObject->getProperties($filter ?? 0),
217: ));
218: }
219:
220: /**
221: * {@inheritDoc}
222: */
223: public function hasConstant($name): bool
224: {
225: if ($name === '') {
226: return false;
227: }
228:
229: return $this->betterReflectionObject->hasConstant($name);
230: }
231:
232: /**
233: * @param int-mask-of<ReflectionClassConstant::IS_*>|null $filter
234: *
235: * @return array<non-empty-string, mixed>
236: */
237: public function getConstants(?int $filter = null): array
238: {
239: return array_map(
240: static fn (BetterReflectionClassConstant $betterConstant) => $betterConstant->getValue(),
241: $this->betterReflectionObject->getConstants($filter ?? 0),
242: );
243: }
244:
245: /**
246: * {@inheritDoc}
247: */
248: #[ReturnTypeWillChange]
249: public function getConstant($name)
250: {
251: if ($name === '') {
252: return false;
253: }
254:
255: $betterReflectionConstant = $this->betterReflectionObject->getConstant($name);
256: if ($betterReflectionConstant === null) {
257: return false;
258: }
259:
260: return $betterReflectionConstant->getValue();
261: }
262:
263: /**
264: * @param string $name
265: * @return ReflectionClassConstant|false
266: */
267: #[ReturnTypeWillChange]
268: public function getReflectionConstant($name)
269: {
270: if ($name === '') {
271: return false;
272: }
273:
274: $betterReflectionConstant = $this->betterReflectionObject->getConstant($name);
275:
276: if ($betterReflectionConstant === null) {
277: return false;
278: }
279:
280: return new ReflectionClassConstant($betterReflectionConstant);
281: }
282:
283: /**
284: * @param int-mask-of<ReflectionClassConstant::IS_*>|null $filter
285: *
286: * @return list<ReflectionClassConstant>
287: */
288: public function getReflectionConstants(?int $filter = null): array
289: {
290: return array_values(array_map(
291: static fn (BetterReflectionClassConstant $betterConstant): ReflectionClassConstant => new ReflectionClassConstant($betterConstant),
292: $this->betterReflectionObject->getConstants($filter ?? 0),
293: ));
294: }
295:
296: /** @return array<class-string, ReflectionClass> */
297: public function getInterfaces(): array
298: {
299: return array_map(
300: static fn (BetterReflectionClass $interface): ReflectionClass => new ReflectionClass($interface),
301: $this->betterReflectionObject->getInterfaces(),
302: );
303: }
304:
305: /** @return list<class-string> */
306: public function getInterfaceNames(): array
307: {
308: return $this->betterReflectionObject->getInterfaceNames();
309: }
310:
311: public function isInterface(): bool
312: {
313: return $this->betterReflectionObject->isInterface();
314: }
315:
316: /** @return array<trait-string, ReflectionClass> */
317: public function getTraits(): array
318: {
319: $traits = $this->betterReflectionObject->getTraits();
320:
321: /** @var list<trait-string> $traitNames */
322: $traitNames = array_map(static fn (BetterReflectionClass $trait): string => $trait->getName(), $traits);
323:
324: return array_combine(
325: $traitNames,
326: array_map(static fn (BetterReflectionClass $trait): ReflectionClass => new ReflectionClass($trait), $traits),
327: );
328: }
329:
330: /** @return list<trait-string> */
331: public function getTraitNames(): array
332: {
333: return $this->betterReflectionObject->getTraitNames();
334: }
335:
336: /** @return array<non-empty-string, non-empty-string> */
337: public function getTraitAliases(): array
338: {
339: return $this->betterReflectionObject->getTraitAliases();
340: }
341:
342: public function isTrait(): bool
343: {
344: return $this->betterReflectionObject->isTrait();
345: }
346:
347: public function isAbstract(): bool
348: {
349: return $this->betterReflectionObject->isAbstract();
350: }
351:
352: public function isFinal(): bool
353: {
354: return $this->betterReflectionObject->isFinal();
355: }
356:
357: public function isReadOnly(): bool
358: {
359: return $this->betterReflectionObject->isReadOnly();
360: }
361:
362: public function getModifiers(): int
363: {
364: return $this->betterReflectionObject->getModifiers();
365: }
366:
367: /**
368: * {@inheritDoc}
369: */
370: public function isInstance($object): bool
371: {
372: return $this->betterReflectionObject->isInstance($object);
373: }
374:
375: /**
376: * @param mixed $arg
377: * @param mixed ...$args
378: *
379: * @return object
380: */
381: #[ReturnTypeWillChange]
382: public function newInstance($arg = null, ...$args)
383: {
384: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
385: }
386:
387: /**
388: * {@inheritDoc}
389: */
390: #[ReturnTypeWillChange]
391: public function newInstanceWithoutConstructor()
392: {
393: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
394: }
395:
396: /**
397: * {@inheritDoc}
398: */
399: #[ReturnTypeWillChange]
400: public function newInstanceArgs(?array $args = null)
401: {
402: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
403: }
404:
405: /**
406: * @param int-mask-of<ReflectionClass::SKIP_*> $options
407: *
408: * @return never
409: */
410: public function newLazyGhost(callable $initializer, int $options = 0): object
411: {
412: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
413: }
414:
415: /**
416: * @param int-mask-of<ReflectionClass::SKIP_*> $options
417: *
418: * @return never
419: */
420: public function newLazyProxy(callable $factory, int $options = 0): object
421: {
422: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
423: }
424:
425: /** @return never */
426: public function markLazyObjectAsInitialized(object $object): object
427: {
428: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
429: }
430:
431: public function getLazyInitializer(object $object): ?callable
432: {
433: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
434: }
435:
436: /** @return never */
437: public function initializeLazyObject(object $object): object
438: {
439: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
440: }
441:
442: /** @return never */
443: public function isUninitializedLazyObject(object $object): bool
444: {
445: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
446: }
447:
448: /** @param int-mask-of<ReflectionClass::SKIP_*> $options */
449: public function resetAsLazyGhost(object $object, callable $initializer, int $options = 0): void
450: {
451: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
452: }
453:
454: /** @param int-mask-of<ReflectionClass::SKIP_*> $options */
455: public function resetAsLazyProxy(object $object, callable $factory, int $options = 0): void
456: {
457: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
458: }
459:
460: /**
461: * {@inheritDoc}
462: * @psalm-mutation-free
463: * @return ReflectionClass|false
464: */
465: #[ReturnTypeWillChange]
466: public function getParentClass()
467: {
468: $parentClass = $this->betterReflectionObject->getParentClass();
469:
470: if ($parentClass === null) {
471: return false;
472: }
473:
474: return new ReflectionClass($parentClass);
475: }
476:
477: /**
478: * {@inheritDoc}
479: */
480: public function isSubclassOf($class): bool
481: {
482: $realParentClassNames = $this->betterReflectionObject->getParentClassNames();
483:
484: $parentClassNames = array_combine(array_map(static fn (string $parentClassName): string => strtolower($parentClassName), $realParentClassNames), $realParentClassNames);
485:
486: $className = $class instanceof CoreReflectionClass ? $class->getName() : $class;
487: $lowercasedClassName = strtolower($className);
488:
489: $realParentClassName = $parentClassNames[$lowercasedClassName] ?? $className;
490:
491: return $this->betterReflectionObject->isSubclassOf($realParentClassName);
492: }
493:
494: /**
495: * @return array<string, mixed>
496: *
497: * @psalm-suppress LessSpecificImplementedReturnType
498: */
499: public function getStaticProperties(): array
500: {
501: return $this->betterReflectionObject->getStaticProperties();
502: }
503:
504: /**
505: * {@inheritDoc}
506: */
507: #[ReturnTypeWillChange]
508: public function getStaticPropertyValue($name, $default = null)
509: {
510: $betterReflectionProperty = $name !== '' ? $this->betterReflectionObject->getProperty($name) : null;
511:
512: if ($betterReflectionProperty === null) {
513: if (func_num_args() === 2) {
514: return $default;
515: }
516:
517: throw new CoreReflectionException(sprintf('Property %s::$%s does not exist', $this->betterReflectionObject->getName(), $name));
518: }
519:
520: $property = new ReflectionProperty($betterReflectionProperty);
521:
522: if (! $property->isStatic()) {
523: throw new CoreReflectionException(sprintf('Property %s::$%s does not exist', $this->betterReflectionObject->getName(), $name));
524: }
525:
526: return $property->getValue();
527: }
528:
529: /**
530: * {@inheritDoc}
531: */
532: public function setStaticPropertyValue($name, $value): void
533: {
534: $betterReflectionProperty = $name !== '' ? $this->betterReflectionObject->getProperty($name) : null;
535:
536: if ($betterReflectionProperty === null) {
537: throw new CoreReflectionException(sprintf('Class %s does not have a property named %s', $this->betterReflectionObject->getName(), $name));
538: }
539:
540: $property = new ReflectionProperty($betterReflectionProperty);
541:
542: if (! $property->isStatic()) {
543: throw new CoreReflectionException(sprintf('Class %s does not have a property named %s', $this->betterReflectionObject->getName(), $name));
544: }
545:
546: $property->setValue($value);
547: }
548:
549: /** @return array<string, scalar|array<scalar>|null> */
550: public function getDefaultProperties(): array
551: {
552: return $this->betterReflectionObject->getDefaultProperties();
553: }
554:
555: public function isIterateable(): bool
556: {
557: return $this->betterReflectionObject->isIterateable();
558: }
559:
560: public function isIterable(): bool
561: {
562: return $this->isIterateable();
563: }
564:
565: /**
566: * @param \ReflectionClass|string $interface
567: */
568: public function implementsInterface($interface): bool
569: {
570: $realInterfaceNames = $this->betterReflectionObject->getInterfaceNames();
571:
572: $interfaceNames = array_combine(array_map(static fn (string $interfaceName): string => strtolower($interfaceName), $realInterfaceNames), $realInterfaceNames);
573:
574: $interfaceName = $interface instanceof CoreReflectionClass ? $interface->getName() : $interface;
575: $lowercasedInterfaceName = strtolower($interfaceName);
576:
577: $realInterfaceName = $interfaceNames[$lowercasedInterfaceName] ?? $interfaceName;
578:
579: return $this->betterReflectionObject->implementsInterface($realInterfaceName);
580: }
581:
582: public function getExtension(): ?CoreReflectionExtension
583: {
584: throw new Exception\NotImplemented('Not implemented');
585: }
586:
587: /**
588: * {@inheritDoc}
589: */
590: #[ReturnTypeWillChange]
591: public function getExtensionName()
592: {
593: return $this->betterReflectionObject->getExtensionName() ?? false;
594: }
595:
596: public function inNamespace(): bool
597: {
598: return $this->betterReflectionObject->inNamespace();
599: }
600:
601: public function getNamespaceName(): string
602: {
603: return $this->betterReflectionObject->getNamespaceName() ?? '';
604: }
605:
606: public function getShortName(): string
607: {
608: return $this->betterReflectionObject->getShortName();
609: }
610:
611: public function isAnonymous(): bool
612: {
613: return $this->betterReflectionObject->isAnonymous();
614: }
615:
616: /**
617: * @param class-string|null $name
618: *
619: * @return list<ReflectionAttribute|FakeReflectionAttribute>
620: */
621: public function getAttributes(?string $name = null, int $flags = 0): array
622: {
623: if ($flags !== 0 && $flags !== ReflectionAttribute::IS_INSTANCEOF) {
624: throw new ValueError('Argument #2 ($flags) must be a valid attribute filter flag');
625: }
626:
627: if ($name !== null && $flags !== 0) {
628: $attributes = $this->betterReflectionObject->getAttributesByInstance($name);
629: } elseif ($name !== null) {
630: $attributes = $this->betterReflectionObject->getAttributesByName($name);
631: } else {
632: $attributes = $this->betterReflectionObject->getAttributes();
633: }
634:
635: return array_map(static fn (BetterReflectionAttribute $betterReflectionAttribute) => ReflectionAttributeFactory::create($betterReflectionAttribute), $attributes);
636: }
637:
638: public function isEnum(): bool
639: {
640: return $this->betterReflectionObject->isEnum();
641: }
642:
643: /**
644: * @return mixed
645: */
646: public function __get(string $name)
647: {
648: if ($name === 'name') {
649: return $this->betterReflectionObject->getName();
650: }
651:
652: throw new OutOfBoundsException(sprintf('Property %s::$%s does not exist.', self::class, $name));
653: }
654: }
655: