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