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 ReflectionMethod as CoreReflectionMethod;
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\ReflectionEnum as BetterReflectionEnum;
17: use PHPStan\BetterReflection\Reflection\ReflectionEnumCase as BetterReflectionEnumCase;
18: use PHPStan\BetterReflection\Reflection\ReflectionMethod as BetterReflectionMethod;
19: use PHPStan\BetterReflection\Reflection\ReflectionProperty as BetterReflectionProperty;
20: use PHPStan\BetterReflection\Util\ClassExistenceChecker;
21: use PHPStan\BetterReflection\Util\FileHelper;
22: use ValueError;
23:
24: use function array_combine;
25: use function array_map;
26: use function array_values;
27: use function constant;
28: use function func_get_args;
29: use function func_num_args;
30: use function sprintf;
31: use function strtolower;
32:
33: /**
34: * @template-extends CoreReflectionClass<object>
35: * @psalm-suppress PropertyNotSetInConstructor
36: */
37: final class ReflectionClass extends CoreReflectionClass
38: {
39: /**
40: * @var BetterReflectionClass|BetterReflectionEnum
41: */
42: private $betterReflectionClass;
43: /**
44: * @internal
45: *
46: * @see CoreReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE_COMPATIBILITY
47: */
48: public const SKIP_INITIALIZATION_ON_SERIALIZE_COMPATIBILITY = 8;
49:
50: /**
51: * @internal
52: *
53: * @see CoreReflectionClass::SKIP_DESTRUCTOR
54: */
55: public const SKIP_DESTRUCTOR_COMPATIBILITY = 16;
56:
57: /** @internal */
58: public const IS_READONLY_COMPATIBILITY = 65536;
59:
60: /**
61: * @param BetterReflectionClass|BetterReflectionEnum $betterReflectionClass
62: */
63: public function __construct($betterReflectionClass)
64: {
65: $this->betterReflectionClass = $betterReflectionClass;
66: /** @phpstan-ignore unset.readOnlyPropertyByPhpDoc */
67: unset($this->name);
68: }
69:
70: /**
71: * @return BetterReflectionClass|BetterReflectionEnum
72: */
73: public function getBetterReflection()
74: {
75: return $this->betterReflectionClass;
76: }
77:
78: /** @return non-empty-string */
79: public function __toString(): string
80: {
81: return $this->betterReflectionClass->__toString();
82: }
83:
84: /**
85: * @return mixed
86: */
87: public function __get(string $name)
88: {
89: if ($name === 'name') {
90: return $this->betterReflectionClass->getName();
91: }
92:
93: throw new OutOfBoundsException(sprintf('Property %s::$%s does not exist.', self::class, $name));
94: }
95:
96: /**
97: * @psalm-mutation-free
98: * @return class-string
99: */
100: public function getName(): string
101: {
102: return $this->betterReflectionClass->getName();
103: }
104:
105: /** @psalm-mutation-free */
106: public function isAnonymous(): bool
107: {
108: return $this->betterReflectionClass->isAnonymous();
109: }
110:
111: /** @psalm-mutation-free */
112: public function isInternal(): bool
113: {
114: return $this->betterReflectionClass->isInternal();
115: }
116:
117: /** @psalm-mutation-free */
118: public function isUserDefined(): bool
119: {
120: return $this->betterReflectionClass->isUserDefined();
121: }
122:
123: /** @psalm-mutation-free */
124: public function isInstantiable(): bool
125: {
126: return $this->betterReflectionClass->isInstantiable();
127: }
128:
129: /** @psalm-mutation-free */
130: public function isCloneable(): bool
131: {
132: return $this->betterReflectionClass->isCloneable();
133: }
134:
135: /**
136: * {@inheritDoc}
137: * @return non-empty-string|false
138: */
139: #[ReturnTypeWillChange]
140: public function getFileName()
141: {
142: $fileName = $this->betterReflectionClass->getFileName();
143:
144: return $fileName !== null ? FileHelper::normalizeSystemPath($fileName) : false;
145: }
146:
147: /**
148: * {@inheritDoc}
149: * @psalm-mutation-free
150: */
151: #[ReturnTypeWillChange]
152: public function getStartLine()
153: {
154: return $this->betterReflectionClass->getStartLine();
155: }
156:
157: /**
158: * {@inheritDoc}
159: * @psalm-mutation-free
160: */
161: #[ReturnTypeWillChange]
162: public function getEndLine()
163: {
164: return $this->betterReflectionClass->getEndLine();
165: }
166:
167: /**
168: * {@inheritDoc}
169: */
170: #[ReturnTypeWillChange]
171: public function getDocComment()
172: {
173: return $this->betterReflectionClass->getDocComment() ?? false;
174: }
175:
176: /**
177: * @psalm-mutation-free
178: * @return ReflectionMethod|null
179: */
180: public function getConstructor(): ?CoreReflectionMethod
181: {
182: $constructor = $this->betterReflectionClass->getConstructor();
183:
184: if ($constructor === null) {
185: return null;
186: }
187:
188: return new ReflectionMethod($constructor);
189: }
190:
191: /**
192: * {@inheritDoc}
193: */
194: public function hasMethod($name): bool
195: {
196: if ($name === '') {
197: return false;
198: }
199:
200: return $this->betterReflectionClass->hasMethod($name);
201: }
202:
203: /**
204: * @param string $name
205: * @return ReflectionMethod
206: */
207: public function getMethod($name): CoreReflectionMethod
208: {
209: $method = $name !== '' ? $this->betterReflectionClass->getMethod($name) : null;
210:
211: if ($method === null) {
212: throw new CoreReflectionException(sprintf('Method %s::%s() does not exist', $this->betterReflectionClass->getName(), $name));
213: }
214:
215: return new ReflectionMethod($method);
216: }
217:
218: /**
219: * @param int-mask-of<ReflectionMethod::IS_*>|null $filter
220: * @return list<ReflectionMethod>
221: */
222: public function getMethods($filter = null): array
223: {
224: /** @psalm-suppress ImpureFunctionCall */
225: return array_values(array_map(
226: static fn (BetterReflectionMethod $method): ReflectionMethod => new ReflectionMethod($method),
227: $this->betterReflectionClass->getMethods($filter ?? 0),
228: ));
229: }
230:
231: /**
232: * {@inheritDoc}
233: */
234: public function hasProperty($name): bool
235: {
236: if ($name === '') {
237: return false;
238: }
239:
240: return $this->betterReflectionClass->hasProperty($name);
241: }
242:
243: /**
244: * @param string $name
245: * @return ReflectionProperty
246: */
247: public function getProperty($name): \ReflectionProperty
248: {
249: $betterReflectionProperty = $name !== '' ? $this->betterReflectionClass->getProperty($name) : null;
250:
251: if ($betterReflectionProperty === null) {
252: throw new CoreReflectionException(sprintf('Property %s::$%s does not exist', $this->betterReflectionClass->getName(), $name));
253: }
254:
255: return new ReflectionProperty($betterReflectionProperty);
256: }
257:
258: /**
259: * @param int-mask-of<ReflectionProperty::IS_*>|null $filter
260: * @return list<ReflectionProperty>
261: */
262: public function getProperties($filter = null): array
263: {
264: /** @psalm-suppress ImpureFunctionCall */
265: return array_values(array_map(
266: static fn (BetterReflectionProperty $property): ReflectionProperty => new ReflectionProperty($property),
267: $this->betterReflectionClass->getProperties($filter ?? 0),
268: ));
269: }
270:
271: /**
272: * {@inheritDoc}
273: */
274: public function hasConstant($name): bool
275: {
276: if ($name === '') {
277: return false;
278: }
279:
280: if ($this->betterReflectionClass instanceof BetterReflectionEnum && $this->betterReflectionClass->hasCase($name)) {
281: return true;
282: }
283:
284: return $this->betterReflectionClass->hasConstant($name);
285: }
286:
287: /**
288: * @param int-mask-of<ReflectionClassConstant::IS_*>|null $filter
289: *
290: * @return array<non-empty-string, mixed>
291: *
292: * @psalm-mutation-free
293: */
294: public function getConstants(?int $filter = null): array
295: {
296: /** @psalm-suppress ImpureFunctionCall */
297: return array_map(
298: fn ($betterConstantOrEnumCase) => $this->getConstantValue($betterConstantOrEnumCase),
299: $this->filterBetterReflectionClassConstants($filter),
300: );
301: }
302:
303: /**
304: * @return mixed
305: */
306: #[ReturnTypeWillChange]
307: public function getConstant($name)
308: {
309: if ($name === '') {
310: return false;
311: }
312:
313: if ($this->betterReflectionClass instanceof BetterReflectionEnum) {
314: $enumCase = $this->betterReflectionClass->getCase($name);
315: if ($enumCase !== null) {
316: return $this->getConstantValue($enumCase);
317: }
318: }
319:
320: $betterReflectionConstant = $this->betterReflectionClass->getConstant($name);
321: if ($betterReflectionConstant === null) {
322: return false;
323: }
324:
325: return $betterReflectionConstant->getValue();
326: }
327:
328: /** @psalm-pure
329: * @param BetterReflectionClassConstant|BetterReflectionEnumCase $betterConstantOrEnumCase
330: * @return mixed */
331: private function getConstantValue($betterConstantOrEnumCase)
332: {
333: if ($betterConstantOrEnumCase instanceof BetterReflectionEnumCase) {
334: throw new Exception\NotImplemented('Not implemented');
335: }
336:
337: return $betterConstantOrEnumCase->getValue();
338: }
339:
340: /**
341: * @param string $name
342: * @return ReflectionClassConstant|false
343: */
344: #[ReturnTypeWillChange]
345: public function getReflectionConstant($name)
346: {
347: if ($name === '') {
348: return false;
349: }
350:
351: if ($this->betterReflectionClass instanceof BetterReflectionEnum) {
352: $enumCase = $this->betterReflectionClass->getCase($name);
353: if ($enumCase !== null) {
354: return new ReflectionClassConstant($enumCase);
355: }
356: }
357:
358: $betterReflectionConstant = $this->betterReflectionClass->getConstant($name);
359: if ($betterReflectionConstant === null) {
360: return false;
361: }
362:
363: return new ReflectionClassConstant($betterReflectionConstant);
364: }
365:
366: /**
367: * @param int-mask-of<ReflectionClassConstant::IS_*>|null $filter
368: *
369: * @return list<ReflectionClassConstant>
370: *
371: * @psalm-mutation-free
372: */
373: public function getReflectionConstants(?int $filter = null): array
374: {
375: return array_values(array_map(
376: static fn ($betterConstantOrEnum): ReflectionClassConstant => new ReflectionClassConstant($betterConstantOrEnum),
377: $this->filterBetterReflectionClassConstants($filter),
378: ));
379: }
380:
381: /**
382: * @param int-mask-of<ReflectionClassConstant::IS_*>|null $filter
383: *
384: * @return array<non-empty-string, BetterReflectionClassConstant|BetterReflectionEnumCase>
385: *
386: * @psalm-mutation-free
387: */
388: private function filterBetterReflectionClassConstants(?int $filter): array
389: {
390: $reflectionConstants = $this->betterReflectionClass->getConstants($filter ?? 0);
391:
392: if (
393: $this->betterReflectionClass instanceof BetterReflectionEnum
394: && (
395: $filter === null
396: || $filter & ReflectionClassConstant::IS_PUBLIC_COMPATIBILITY
397: )
398: ) {
399: $reflectionConstants += $this->betterReflectionClass->getCases();
400: }
401:
402: return $reflectionConstants;
403: }
404:
405: /** @return list<class-string> */
406: public function getInterfaceClassNames(): array
407: {
408: return $this->betterReflectionClass->getInterfaceClassNames();
409: }
410:
411: /**
412: * @psalm-mutation-free
413: * @return array<class-string, self>
414: */
415: public function getInterfaces(): array
416: {
417: /** @psalm-suppress ImpureFunctionCall */
418: return array_map(
419: static fn (BetterReflectionClass $interface): self => new self($interface),
420: $this->betterReflectionClass->getInterfaces(),
421: );
422: }
423:
424: /**
425: * @return list<class-string>
426: *
427: * @psalm-mutation-free
428: */
429: public function getInterfaceNames(): array
430: {
431: return $this->betterReflectionClass->getInterfaceNames();
432: }
433:
434: /** @psalm-mutation-free */
435: public function isInterface(): bool
436: {
437: return $this->betterReflectionClass->isInterface();
438: }
439:
440: /** @return list<trait-string> */
441: public function getTraitClassNames(): array
442: {
443: return $this->betterReflectionClass->getTraitClassNames();
444: }
445:
446: /**
447: * @psalm-mutation-free
448: * @return array<trait-string, self>
449: */
450: public function getTraits(): array
451: {
452: $traits = $this->betterReflectionClass->getTraits();
453:
454: $traitsByName = [];
455: foreach ($traits as $trait) {
456: /** @var trait-string $traitName */
457: $traitName = $trait->getName();
458: $traitsByName[$traitName] = new self($trait);
459: }
460:
461: return $traitsByName;
462: }
463:
464: /**
465: * @return list<trait-string>
466: *
467: * @psalm-mutation-free
468: */
469: public function getTraitNames(): array
470: {
471: return $this->betterReflectionClass->getTraitNames();
472: }
473:
474: /**
475: * @return array<non-empty-string, non-empty-string>
476: *
477: * @psalm-mutation-free
478: */
479: public function getTraitAliases(): array
480: {
481: return $this->betterReflectionClass->getTraitAliases();
482: }
483:
484: /** @psalm-mutation-free */
485: public function isTrait(): bool
486: {
487: return $this->betterReflectionClass->isTrait();
488: }
489:
490: /** @psalm-mutation-free */
491: public function isAbstract(): bool
492: {
493: return $this->betterReflectionClass->isAbstract();
494: }
495:
496: /** @psalm-mutation-free */
497: public function isFinal(): bool
498: {
499: return $this->betterReflectionClass->isFinal();
500: }
501:
502: /** @psalm-mutation-free */
503: public function isReadOnly(): bool
504: {
505: return $this->betterReflectionClass->isReadOnly();
506: }
507:
508: /** @psalm-mutation-free */
509: public function getModifiers(): int
510: {
511: return $this->betterReflectionClass->getModifiers();
512: }
513:
514: /**
515: * {@inheritDoc}
516: */
517: public function isInstance($object): bool
518: {
519: return $this->betterReflectionClass->isInstance($object);
520: }
521:
522: /**
523: * @return object
524: * @param mixed $arg
525: * @param mixed $args
526: */
527: #[ReturnTypeWillChange]
528: public function newInstance($arg = null, ...$args)
529: {
530: ClassExistenceChecker::classExists($this->getName(), true);
531: $reflection = new CoreReflectionClass($this->getName());
532:
533: return $reflection->newInstance(...func_get_args());
534: }
535:
536: public function newInstanceWithoutConstructor(): object
537: {
538: ClassExistenceChecker::classExists($this->getName(), true);
539: $reflection = new CoreReflectionClass($this->getName());
540:
541: return $reflection->newInstanceWithoutConstructor();
542: }
543:
544: public function newInstanceArgs(?array $args = null): object
545: {
546: ClassExistenceChecker::classExists($this->getName(), true);
547: $reflection = new CoreReflectionClass($this->getName());
548:
549: return $reflection->newInstanceArgs($args);
550: }
551:
552: /**
553: * @param int-mask-of<self::SKIP_*> $options
554: *
555: * @return never
556: */
557: public function newLazyGhost(callable $initializer, int $options = 0): object
558: {
559: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
560: }
561:
562: /**
563: * @param int-mask-of<self::SKIP_*> $options
564: *
565: * @return never
566: */
567: public function newLazyProxy(callable $factory, int $options = 0): object
568: {
569: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
570: }
571:
572: /** @return never */
573: public function markLazyObjectAsInitialized(object $object): object
574: {
575: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
576: }
577:
578: public function getLazyInitializer(object $object): ?callable
579: {
580: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
581: }
582:
583: /** @return never */
584: public function initializeLazyObject(object $object): object
585: {
586: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
587: }
588:
589: /** @return never */
590: public function isUninitializedLazyObject(object $object): bool
591: {
592: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
593: }
594:
595: /** @param int-mask-of<self::SKIP_*> $options */
596: public function resetAsLazyGhost(object $object, callable $initializer, int $options = 0): void
597: {
598: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
599: }
600:
601: /** @param int-mask-of<self::SKIP_*> $options */
602: public function resetAsLazyProxy(object $object, callable $factory, int $options = 0): void
603: {
604: throw Exception\NotImplementedBecauseItTriggersAutoloading::create();
605: }
606:
607: /** @return class-string|null */
608: public function getParentClassName(): ?string
609: {
610: return $this->betterReflectionClass->getParentClassName();
611: }
612:
613: /**
614: * {@inheritDoc}
615: * @psalm-mutation-free
616: * @return self|false
617: */
618: #[ReturnTypeWillChange]
619: public function getParentClass()
620: {
621: $parentClass = $this->betterReflectionClass->getParentClass();
622:
623: if ($parentClass === null) {
624: return false;
625: }
626:
627: return new self($parentClass);
628: }
629:
630: /**
631: * {@inheritDoc}
632: */
633: public function isSubclassOf($class): bool
634: {
635: $realParentClassNames = $this->betterReflectionClass->getParentClassNames();
636:
637: $parentClassNames = array_combine(array_map(static fn (string $parentClassName): string => strtolower($parentClassName), $realParentClassNames), $realParentClassNames);
638:
639: $className = $class instanceof CoreReflectionClass ? $class->getName() : $class;
640: $lowercasedClassName = strtolower($className);
641:
642: $realParentClassName = $parentClassNames[$lowercasedClassName] ?? $className;
643:
644: if ($this->betterReflectionClass->isSubclassOf($realParentClassName)) {
645: return true;
646: }
647:
648: return $this->implementsInterface($className);
649: }
650:
651: /**
652: * @return array<string, mixed>
653: *
654: * @psalm-suppress LessSpecificImplementedReturnType
655: */
656: public function getStaticProperties(): array
657: {
658: return $this->betterReflectionClass->getStaticProperties();
659: }
660:
661: /**
662: * {@inheritDoc}
663: */
664: #[ReturnTypeWillChange]
665: public function getStaticPropertyValue($name, $default = null)
666: {
667: $betterReflectionProperty = $name !== '' ? $this->betterReflectionClass->getProperty($name) : null;
668:
669: if ($betterReflectionProperty === null) {
670: if (func_num_args() === 2) {
671: return $default;
672: }
673:
674: throw new CoreReflectionException(sprintf('Property %s::$%s does not exist', $this->betterReflectionClass->getName(), $name));
675: }
676:
677: $property = new ReflectionProperty($betterReflectionProperty);
678:
679: if (! $property->isStatic()) {
680: throw new CoreReflectionException(sprintf('Property %s::$%s does not exist', $this->betterReflectionClass->getName(), $name));
681: }
682:
683: return $property->getValue();
684: }
685:
686: /**
687: * {@inheritDoc}
688: */
689: public function setStaticPropertyValue($name, $value): void
690: {
691: $betterReflectionProperty = $name !== '' ? $this->betterReflectionClass->getProperty($name) : null;
692:
693: if ($betterReflectionProperty === null) {
694: throw new CoreReflectionException(sprintf('Class %s does not have a property named %s', $this->betterReflectionClass->getName(), $name));
695: }
696:
697: $property = new ReflectionProperty($betterReflectionProperty);
698:
699: if (! $property->isStatic()) {
700: throw new CoreReflectionException(sprintf('Class %s does not have a property named %s', $this->betterReflectionClass->getName(), $name));
701: }
702:
703: $property->setValue($value);
704: }
705:
706: /**
707: * @return array<non-empty-string, mixed>
708: *
709: * @psalm-mutation-free
710: */
711: public function getDefaultProperties(): array
712: {
713: return $this->betterReflectionClass->getDefaultProperties();
714: }
715:
716: /** @psalm-mutation-free */
717: public function isIterateable(): bool
718: {
719: return $this->betterReflectionClass->isIterateable();
720: }
721:
722: /** @psalm-mutation-free */
723: public function isIterable(): bool
724: {
725: return $this->isIterateable();
726: }
727:
728: /**
729: * @param \ReflectionClass|string $interface
730: */
731: public function implementsInterface($interface): bool
732: {
733: $realInterfaceNames = $this->betterReflectionClass->getInterfaceNames();
734:
735: $interfaceNames = array_combine(array_map(static fn (string $interfaceName): string => strtolower($interfaceName), $realInterfaceNames), $realInterfaceNames);
736:
737: $interfaceName = $interface instanceof CoreReflectionClass ? $interface->getName() : $interface;
738: $lowercasedInterfaceName = strtolower($interfaceName);
739:
740: $realInterfaceName = $interfaceNames[$lowercasedInterfaceName] ?? $interfaceName;
741:
742: return $this->betterReflectionClass->implementsInterface($realInterfaceName);
743: }
744:
745: /** @psalm-mutation-free */
746: public function getExtension(): ?CoreReflectionExtension
747: {
748: throw new Exception\NotImplemented('Not implemented');
749: }
750:
751: /**
752: * {@inheritDoc}
753: */
754: #[ReturnTypeWillChange]
755: public function getExtensionName()
756: {
757: return $this->betterReflectionClass->getExtensionName() ?? false;
758: }
759:
760: /** @psalm-mutation-free */
761: public function inNamespace(): bool
762: {
763: return $this->betterReflectionClass->inNamespace();
764: }
765:
766: /** @psalm-mutation-free */
767: public function getNamespaceName(): string
768: {
769: return $this->betterReflectionClass->getNamespaceName() ?? '';
770: }
771:
772: /** @psalm-mutation-free */
773: public function getShortName(): string
774: {
775: return $this->betterReflectionClass->getShortName();
776: }
777:
778: /**
779: * @param class-string|null $name
780: *
781: * @return list<ReflectionAttribute|FakeReflectionAttribute>
782: */
783: public function getAttributes(?string $name = null, int $flags = 0): array
784: {
785: if ($flags !== 0 && $flags !== ReflectionAttribute::IS_INSTANCEOF) {
786: throw new ValueError('Argument #2 ($flags) must be a valid attribute filter flag');
787: }
788:
789: if ($name !== null && $flags !== 0) {
790: $attributes = $this->betterReflectionClass->getAttributesByInstance($name);
791: } elseif ($name !== null) {
792: $attributes = $this->betterReflectionClass->getAttributesByName($name);
793: } else {
794: $attributes = $this->betterReflectionClass->getAttributes();
795: }
796:
797: /** @psalm-suppress ImpureFunctionCall */
798: return array_map(static fn (BetterReflectionAttribute $betterReflectionAttribute) => ReflectionAttributeFactory::create($betterReflectionAttribute), $attributes);
799: }
800:
801: /** @psalm-mutation-free */
802: public function isEnum(): bool
803: {
804: return $this->betterReflectionClass->isEnum();
805: }
806: }
807: