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