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: /**
378: * @psalm-mutation-free
379: * @return array<class-string, self>
380: */
381: public function getInterfaces(): array
382: {
383: /** @psalm-suppress ImpureFunctionCall */
384: return array_map(static function (BetterReflectionClass $interface) : self {
385: return new self($interface);
386: }, $this->betterReflectionClass->getInterfaces());
387: }
388:
389: /**
390: * @return list<class-string>
391: *
392: * @psalm-mutation-free
393: */
394: public function getInterfaceNames(): array
395: {
396: return $this->betterReflectionClass->getInterfaceNames();
397: }
398:
399: /** @psalm-mutation-free */
400: public function isInterface(): bool
401: {
402: return $this->betterReflectionClass->isInterface();
403: }
404:
405: /**
406: * @psalm-mutation-free
407: * @return array<trait-string, self>
408: */
409: public function getTraits(): array
410: {
411: $traits = $this->betterReflectionClass->getTraits();
412:
413: /** @var list<trait-string> $traitNames */
414: $traitNames = array_map(static function (BetterReflectionClass $trait) : string {
415: return $trait->getName();
416: }, $traits);
417:
418: /** @psalm-suppress ImpureFunctionCall */
419: return array_combine($traitNames, array_map(static function (BetterReflectionClass $trait) : self {
420: return new self($trait);
421: }, $traits));
422: }
423:
424: /**
425: * @return list<trait-string>
426: *
427: * @psalm-mutation-free
428: */
429: public function getTraitNames(): array
430: {
431: return $this->betterReflectionClass->getTraitNames();
432: }
433:
434: /**
435: * @return array<non-empty-string, non-empty-string>
436: *
437: * @psalm-mutation-free
438: */
439: public function getTraitAliases(): array
440: {
441: return $this->betterReflectionClass->getTraitAliases();
442: }
443:
444: /** @psalm-mutation-free */
445: public function isTrait(): bool
446: {
447: return $this->betterReflectionClass->isTrait();
448: }
449:
450: /** @psalm-mutation-free */
451: public function isAbstract(): bool
452: {
453: return $this->betterReflectionClass->isAbstract();
454: }
455:
456: /** @psalm-mutation-free */
457: public function isFinal(): bool
458: {
459: return $this->betterReflectionClass->isFinal();
460: }
461:
462: /** @psalm-mutation-free */
463: public function isReadOnly(): bool
464: {
465: return $this->betterReflectionClass->isReadOnly();
466: }
467:
468: /** @psalm-mutation-free */
469: public function getModifiers(): int
470: {
471: return $this->betterReflectionClass->getModifiers();
472: }
473:
474: /**
475: * {@inheritDoc}
476: */
477: public function isInstance($object): bool
478: {
479: return $this->betterReflectionClass->isInstance($object);
480: }
481:
482: /**
483: * @return object
484: * @param mixed $arg
485: * @param mixed $args
486: */
487: #[ReturnTypeWillChange]
488: public function newInstance($arg = null, ...$args)
489: {
490: ClassExistenceChecker::classExists($this->getName(), true);
491: $reflection = new CoreReflectionClass($this->getName());
492:
493: return $reflection->newInstance(...func_get_args());
494: }
495:
496: public function newInstanceWithoutConstructor(): object
497: {
498: ClassExistenceChecker::classExists($this->getName(), true);
499: $reflection = new CoreReflectionClass($this->getName());
500:
501: return $reflection->newInstanceWithoutConstructor();
502: }
503:
504: public function newInstanceArgs(array $args = null): object
505: {
506: ClassExistenceChecker::classExists($this->getName(), true);
507: $reflection = new CoreReflectionClass($this->getName());
508:
509: return $reflection->newInstanceArgs($args);
510: }
511:
512: /**
513: * @return self|false
514: */
515: #[ReturnTypeWillChange]
516: public function getParentClass()
517: {
518: $parentClass = $this->betterReflectionClass->getParentClass();
519:
520: if ($parentClass === null) {
521: return false;
522: }
523:
524: return new self($parentClass);
525: }
526:
527: /**
528: * {@inheritDoc}
529: */
530: public function isSubclassOf($class): bool
531: {
532: $realParentClassNames = $this->betterReflectionClass->getParentClassNames();
533:
534: $parentClassNames = array_combine(array_map(static function (string $parentClassName) : string {
535: return strtolower($parentClassName);
536: }, $realParentClassNames), $realParentClassNames);
537:
538: $className = $class instanceof CoreReflectionClass ? $class->getName() : $class;
539: $lowercasedClassName = strtolower($className);
540:
541: $realParentClassName = $parentClassNames[$lowercasedClassName] ?? $className;
542:
543: return $this->betterReflectionClass->isSubclassOf($realParentClassName) || $this->implementsInterface($className);
544: }
545:
546: /**
547: * @return array<string, mixed>
548: *
549: * @psalm-suppress LessSpecificImplementedReturnType
550: */
551: public function getStaticProperties(): array
552: {
553: return $this->betterReflectionClass->getStaticProperties();
554: }
555:
556: /**
557: * {@inheritDoc}
558: */
559: #[ReturnTypeWillChange]
560: public function getStaticPropertyValue($name, $default = null)
561: {
562: $betterReflectionProperty = $name !== '' ? $this->betterReflectionClass->getProperty($name) : null;
563:
564: if ($betterReflectionProperty === null) {
565: if (func_num_args() === 2) {
566: return $default;
567: }
568:
569: throw new CoreReflectionException(sprintf('Property %s::$%s does not exist', $this->betterReflectionClass->getName(), $name));
570: }
571:
572: $property = new ReflectionProperty($betterReflectionProperty);
573:
574: if (! $property->isStatic()) {
575: throw new CoreReflectionException(sprintf('Property %s::$%s does not exist', $this->betterReflectionClass->getName(), $name));
576: }
577:
578: return $property->getValue();
579: }
580:
581: /**
582: * {@inheritDoc}
583: */
584: public function setStaticPropertyValue($name, $value): void
585: {
586: $betterReflectionProperty = $name !== '' ? $this->betterReflectionClass->getProperty($name) : null;
587:
588: if ($betterReflectionProperty === null) {
589: throw new CoreReflectionException(sprintf('Class %s does not have a property named %s', $this->betterReflectionClass->getName(), $name));
590: }
591:
592: $property = new ReflectionProperty($betterReflectionProperty);
593:
594: if (! $property->isStatic()) {
595: throw new CoreReflectionException(sprintf('Class %s does not have a property named %s', $this->betterReflectionClass->getName(), $name));
596: }
597:
598: $property->setValue($value);
599: }
600:
601: /**
602: * @return array<non-empty-string, mixed>
603: *
604: * @psalm-mutation-free
605: */
606: public function getDefaultProperties(): array
607: {
608: return $this->betterReflectionClass->getDefaultProperties();
609: }
610:
611: /** @psalm-mutation-free */
612: public function isIterateable(): bool
613: {
614: return $this->betterReflectionClass->isIterateable();
615: }
616:
617: /** @psalm-mutation-free */
618: public function isIterable(): bool
619: {
620: return $this->isIterateable();
621: }
622:
623: /**
624: * @param \ReflectionClass|string $interface
625: */
626: public function implementsInterface($interface): bool
627: {
628: $realInterfaceNames = $this->betterReflectionClass->getInterfaceNames();
629:
630: $interfaceNames = array_combine(array_map(static function (string $interfaceName) : string {
631: return strtolower($interfaceName);
632: }, $realInterfaceNames), $realInterfaceNames);
633:
634: $interfaceName = $interface instanceof CoreReflectionClass ? $interface->getName() : $interface;
635: $lowercasedIntefaceName = strtolower($interfaceName);
636:
637: $realInterfaceName = $interfaceNames[$lowercasedIntefaceName] ?? $interfaceName;
638:
639: return $this->betterReflectionClass->implementsInterface($realInterfaceName);
640: }
641:
642: /** @psalm-mutation-free */
643: public function getExtension(): ?CoreReflectionExtension
644: {
645: throw new Exception\NotImplemented('Not implemented');
646: }
647:
648: /**
649: * {@inheritDoc}
650: */
651: #[ReturnTypeWillChange]
652: public function getExtensionName()
653: {
654: return $this->betterReflectionClass->getExtensionName() ?? false;
655: }
656:
657: /** @psalm-mutation-free */
658: public function inNamespace(): bool
659: {
660: return $this->betterReflectionClass->inNamespace();
661: }
662:
663: /** @psalm-mutation-free */
664: public function getNamespaceName(): string
665: {
666: return $this->betterReflectionClass->getNamespaceName() ?? '';
667: }
668:
669: /** @psalm-mutation-free */
670: public function getShortName(): string
671: {
672: return $this->betterReflectionClass->getShortName();
673: }
674:
675: /**
676: * @param class-string|null $name
677: *
678: * @return list<ReflectionAttribute|FakeReflectionAttribute>
679: */
680: public function getAttributes(?string $name = null, int $flags = 0): array
681: {
682: if ($flags !== 0 && $flags !== ReflectionAttribute::IS_INSTANCEOF) {
683: throw new ValueError('Argument #2 ($flags) must be a valid attribute filter flag');
684: }
685:
686: if ($name !== null && $flags !== 0) {
687: $attributes = $this->betterReflectionClass->getAttributesByInstance($name);
688: } elseif ($name !== null) {
689: $attributes = $this->betterReflectionClass->getAttributesByName($name);
690: } else {
691: $attributes = $this->betterReflectionClass->getAttributes();
692: }
693:
694: /** @psalm-suppress ImpureFunctionCall */
695: return array_map(static function (BetterReflectionAttribute $betterReflectionAttribute) {
696: return ReflectionAttributeFactory::create($betterReflectionAttribute);
697: }, $attributes);
698: }
699:
700: /** @psalm-mutation-free */
701: public function isEnum(): bool
702: {
703: return $this->betterReflectionClass->isEnum();
704: }
705: }
706: