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