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: * @deprecated Use getReflectionConstants()
261: *
262: * @param int-mask-of<ReflectionClassConstant::IS_*>|null $filter
263: *
264: * @return array<non-empty-string, mixed>
265: *
266: * @psalm-mutation-free
267: */
268: public function getConstants(?int $filter = null): array
269: {
270: /** @psalm-suppress ImpureFunctionCall */
271: return array_map(function ($betterConstantOrEnumCase) {
272: return $this->getConstantValue($betterConstantOrEnumCase);
273: }, $this->filterBetterReflectionClassConstants($filter));
274: }
275:
276: /**
277: * @deprecated Use getReflectionConstant()
278: * @return mixed
279: */
280: #[ReturnTypeWillChange]
281: public function getConstant($name)
282: {
283: if ($name === '') {
284: return false;
285: }
286:
287: if ($this->betterReflectionClass instanceof BetterReflectionEnum) {
288: $enumCase = $this->betterReflectionClass->getCase($name);
289: if ($enumCase !== null) {
290: return $this->getConstantValue($enumCase);
291: }
292: }
293:
294: $betterReflectionConstant = $this->betterReflectionClass->getConstant($name);
295: if ($betterReflectionConstant === null) {
296: return false;
297: }
298:
299: return $betterReflectionConstant->getValue();
300: }
301:
302: /** @psalm-pure
303: * @param BetterReflectionClassConstant|BetterReflectionEnumCase $betterConstantOrEnumCase
304: * @return mixed */
305: private function getConstantValue($betterConstantOrEnumCase)
306: {
307: if ($betterConstantOrEnumCase instanceof BetterReflectionEnumCase) {
308: throw new Exception\NotImplemented('Not implemented');
309: }
310:
311: return $betterConstantOrEnumCase->getValue();
312: }
313:
314: /**
315: * @param string $name
316: * @return ReflectionClassConstant|false
317: */
318: #[ReturnTypeWillChange]
319: public function getReflectionConstant($name)
320: {
321: if ($name === '') {
322: return false;
323: }
324:
325: if ($this->betterReflectionClass instanceof BetterReflectionEnum) {
326: $enumCase = $this->betterReflectionClass->getCase($name);
327: if ($enumCase !== null) {
328: return new ReflectionClassConstant($enumCase);
329: }
330: }
331:
332: $betterReflectionConstant = $this->betterReflectionClass->getConstant($name);
333: if ($betterReflectionConstant === null) {
334: return false;
335: }
336:
337: return new ReflectionClassConstant($betterReflectionConstant);
338: }
339:
340: /**
341: * @param int-mask-of<ReflectionClassConstant::IS_*>|null $filter
342: *
343: * @return list<ReflectionClassConstant>
344: *
345: * @psalm-mutation-free
346: */
347: public function getReflectionConstants(?int $filter = null): array
348: {
349: return array_values(array_map(static function ($betterConstantOrEnum) : ReflectionClassConstant {
350: return new ReflectionClassConstant($betterConstantOrEnum);
351: }, $this->filterBetterReflectionClassConstants($filter)));
352: }
353:
354: /**
355: * @param int-mask-of<ReflectionClassConstant::IS_*>|null $filter
356: *
357: * @return array<non-empty-string, BetterReflectionClassConstant|BetterReflectionEnumCase>
358: *
359: * @psalm-mutation-free
360: */
361: private function filterBetterReflectionClassConstants(?int $filter): array
362: {
363: $reflectionConstants = $this->betterReflectionClass->getConstants($filter ?? 0);
364:
365: if (
366: $this->betterReflectionClass instanceof BetterReflectionEnum
367: && (
368: $filter === null
369: || $filter & ReflectionClassConstant::IS_PUBLIC_COMPATIBILITY
370: )
371: ) {
372: $reflectionConstants += $this->betterReflectionClass->getCases();
373: }
374:
375: return $reflectionConstants;
376: }
377:
378: /** @return list<class-string> */
379: public function getInterfaceClassNames(): array
380: {
381: return $this->betterReflectionClass->getInterfaceClassNames();
382: }
383:
384: /**
385: * @psalm-mutation-free
386: * @return array<class-string, self>
387: */
388: public function getInterfaces(): array
389: {
390: /** @psalm-suppress ImpureFunctionCall */
391: return array_map(static function (BetterReflectionClass $interface) : self {
392: return new self($interface);
393: }, $this->betterReflectionClass->getInterfaces());
394: }
395:
396: /**
397: * @return list<class-string>
398: *
399: * @psalm-mutation-free
400: */
401: public function getInterfaceNames(): array
402: {
403: return $this->betterReflectionClass->getInterfaceNames();
404: }
405:
406: /** @psalm-mutation-free */
407: public function isInterface(): bool
408: {
409: return $this->betterReflectionClass->isInterface();
410: }
411:
412: /** @return list<trait-string> */
413: public function getTraitClassNames(): array
414: {
415: return $this->betterReflectionClass->getTraitClassNames();
416: }
417:
418: /**
419: * @psalm-mutation-free
420: * @return array<trait-string, self>
421: */
422: public function getTraits(): array
423: {
424: $traits = $this->betterReflectionClass->getTraits();
425:
426: /** @var list<trait-string> $traitNames */
427: $traitNames = array_map(static function (BetterReflectionClass $trait) : string {
428: return $trait->getName();
429: }, $traits);
430:
431: /** @psalm-suppress ImpureFunctionCall */
432: return array_combine($traitNames, array_map(static function (BetterReflectionClass $trait) : self {
433: return new self($trait);
434: }, $traits));
435: }
436:
437: /**
438: * @return list<trait-string>
439: *
440: * @psalm-mutation-free
441: */
442: public function getTraitNames(): array
443: {
444: return $this->betterReflectionClass->getTraitNames();
445: }
446:
447: /**
448: * @return array<non-empty-string, non-empty-string>
449: *
450: * @psalm-mutation-free
451: */
452: public function getTraitAliases(): array
453: {
454: return $this->betterReflectionClass->getTraitAliases();
455: }
456:
457: /** @psalm-mutation-free */
458: public function isTrait(): bool
459: {
460: return $this->betterReflectionClass->isTrait();
461: }
462:
463: /** @psalm-mutation-free */
464: public function isAbstract(): bool
465: {
466: return $this->betterReflectionClass->isAbstract();
467: }
468:
469: /** @psalm-mutation-free */
470: public function isFinal(): bool
471: {
472: return $this->betterReflectionClass->isFinal();
473: }
474:
475: /** @psalm-mutation-free */
476: public function isReadOnly(): bool
477: {
478: return $this->betterReflectionClass->isReadOnly();
479: }
480:
481: /** @psalm-mutation-free */
482: public function getModifiers(): int
483: {
484: return $this->betterReflectionClass->getModifiers();
485: }
486:
487: /**
488: * {@inheritDoc}
489: */
490: public function isInstance($object): bool
491: {
492: return $this->betterReflectionClass->isInstance($object);
493: }
494:
495: /**
496: * @return object
497: * @param mixed $arg
498: * @param mixed $args
499: */
500: #[ReturnTypeWillChange]
501: public function newInstance($arg = null, ...$args)
502: {
503: ClassExistenceChecker::classExists($this->getName(), true);
504: $reflection = new CoreReflectionClass($this->getName());
505:
506: return $reflection->newInstance(...func_get_args());
507: }
508:
509: public function newInstanceWithoutConstructor(): object
510: {
511: ClassExistenceChecker::classExists($this->getName(), true);
512: $reflection = new CoreReflectionClass($this->getName());
513:
514: return $reflection->newInstanceWithoutConstructor();
515: }
516:
517: public function newInstanceArgs(?array $args = null): object
518: {
519: ClassExistenceChecker::classExists($this->getName(), true);
520: $reflection = new CoreReflectionClass($this->getName());
521:
522: return $reflection->newInstanceArgs($args);
523: }
524:
525: /** @return class-string|null */
526: public function getParentClassName(): ?string
527: {
528: return $this->betterReflectionClass->getParentClassName();
529: }
530:
531: /**
532: * @return self|false
533: */
534: #[ReturnTypeWillChange]
535: public function getParentClass()
536: {
537: $parentClass = $this->betterReflectionClass->getParentClass();
538:
539: if ($parentClass === null) {
540: return false;
541: }
542:
543: return new self($parentClass);
544: }
545:
546: /**
547: * {@inheritDoc}
548: */
549: public function isSubclassOf($class): bool
550: {
551: $realParentClassNames = $this->betterReflectionClass->getParentClassNames();
552:
553: $parentClassNames = array_combine(array_map(static function (string $parentClassName) : string {
554: return strtolower($parentClassName);
555: }, $realParentClassNames), $realParentClassNames);
556:
557: $className = $class instanceof CoreReflectionClass ? $class->getName() : $class;
558: $lowercasedClassName = strtolower($className);
559:
560: $realParentClassName = $parentClassNames[$lowercasedClassName] ?? $className;
561:
562: if ($this->betterReflectionClass->isSubclassOf($realParentClassName)) {
563: return true;
564: }
565:
566: return $this->implementsInterface($className);
567: }
568:
569: /**
570: * @return array<string, mixed>
571: *
572: * @psalm-suppress LessSpecificImplementedReturnType
573: */
574: public function getStaticProperties(): array
575: {
576: return $this->betterReflectionClass->getStaticProperties();
577: }
578:
579: /**
580: * {@inheritDoc}
581: */
582: #[ReturnTypeWillChange]
583: public function getStaticPropertyValue($name, $default = null)
584: {
585: $betterReflectionProperty = $name !== '' ? $this->betterReflectionClass->getProperty($name) : null;
586:
587: if ($betterReflectionProperty === null) {
588: if (func_num_args() === 2) {
589: return $default;
590: }
591:
592: throw new CoreReflectionException(sprintf('Property %s::$%s does not exist', $this->betterReflectionClass->getName(), $name));
593: }
594:
595: $property = new ReflectionProperty($betterReflectionProperty);
596:
597: if (! $property->isStatic()) {
598: throw new CoreReflectionException(sprintf('Property %s::$%s does not exist', $this->betterReflectionClass->getName(), $name));
599: }
600:
601: return $property->getValue();
602: }
603:
604: /**
605: * {@inheritDoc}
606: */
607: public function setStaticPropertyValue($name, $value): void
608: {
609: $betterReflectionProperty = $name !== '' ? $this->betterReflectionClass->getProperty($name) : null;
610:
611: if ($betterReflectionProperty === null) {
612: throw new CoreReflectionException(sprintf('Class %s does not have a property named %s', $this->betterReflectionClass->getName(), $name));
613: }
614:
615: $property = new ReflectionProperty($betterReflectionProperty);
616:
617: if (! $property->isStatic()) {
618: throw new CoreReflectionException(sprintf('Class %s does not have a property named %s', $this->betterReflectionClass->getName(), $name));
619: }
620:
621: $property->setValue($value);
622: }
623:
624: /**
625: * @return array<non-empty-string, mixed>
626: *
627: * @psalm-mutation-free
628: */
629: public function getDefaultProperties(): array
630: {
631: return $this->betterReflectionClass->getDefaultProperties();
632: }
633:
634: /** @psalm-mutation-free */
635: public function isIterateable(): bool
636: {
637: return $this->betterReflectionClass->isIterateable();
638: }
639:
640: /** @psalm-mutation-free */
641: public function isIterable(): bool
642: {
643: return $this->isIterateable();
644: }
645:
646: /**
647: * @param \ReflectionClass|string $interface
648: */
649: public function implementsInterface($interface): bool
650: {
651: $realInterfaceNames = $this->betterReflectionClass->getInterfaceNames();
652:
653: $interfaceNames = array_combine(array_map(static function (string $interfaceName) : string {
654: return strtolower($interfaceName);
655: }, $realInterfaceNames), $realInterfaceNames);
656:
657: $interfaceName = $interface instanceof CoreReflectionClass ? $interface->getName() : $interface;
658: $lowercasedInterfaceName = strtolower($interfaceName);
659:
660: $realInterfaceName = $interfaceNames[$lowercasedInterfaceName] ?? $interfaceName;
661:
662: return $this->betterReflectionClass->implementsInterface($realInterfaceName);
663: }
664:
665: /** @psalm-mutation-free */
666: public function getExtension(): ?CoreReflectionExtension
667: {
668: throw new Exception\NotImplemented('Not implemented');
669: }
670:
671: /**
672: * {@inheritDoc}
673: */
674: #[ReturnTypeWillChange]
675: public function getExtensionName()
676: {
677: return $this->betterReflectionClass->getExtensionName() ?? false;
678: }
679:
680: /** @psalm-mutation-free */
681: public function inNamespace(): bool
682: {
683: return $this->betterReflectionClass->inNamespace();
684: }
685:
686: /** @psalm-mutation-free */
687: public function getNamespaceName(): string
688: {
689: return $this->betterReflectionClass->getNamespaceName() ?? '';
690: }
691:
692: /** @psalm-mutation-free */
693: public function getShortName(): string
694: {
695: return $this->betterReflectionClass->getShortName();
696: }
697:
698: /**
699: * @param class-string|null $name
700: *
701: * @return list<ReflectionAttribute|FakeReflectionAttribute>
702: */
703: public function getAttributes(?string $name = null, int $flags = 0): array
704: {
705: if ($flags !== 0 && $flags !== ReflectionAttribute::IS_INSTANCEOF) {
706: throw new ValueError('Argument #2 ($flags) must be a valid attribute filter flag');
707: }
708:
709: if ($name !== null && $flags !== 0) {
710: $attributes = $this->betterReflectionClass->getAttributesByInstance($name);
711: } elseif ($name !== null) {
712: $attributes = $this->betterReflectionClass->getAttributesByName($name);
713: } else {
714: $attributes = $this->betterReflectionClass->getAttributes();
715: }
716:
717: /** @psalm-suppress ImpureFunctionCall */
718: return array_map(static function (BetterReflectionAttribute $betterReflectionAttribute) {
719: return ReflectionAttributeFactory::create($betterReflectionAttribute);
720: }, $attributes);
721: }
722:
723: /** @psalm-mutation-free */
724: public function isEnum(): bool
725: {
726: return $this->betterReflectionClass->isEnum();
727: }
728: }
729: