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