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