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