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