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