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