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