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: * @deprecated Use getReflectionConstants()
201: *
202: * @param int-mask-of<ReflectionClassConstant::IS_*>|null $filter
203: *
204: * @return array<non-empty-string, mixed>
205: */
206: public function getConstants(int|null $filter = null): array
207: {
208: /** @psalm-suppress ImpureFunctionCall */
209: return array_map(
210: fn (BetterReflectionClassConstant|BetterReflectionEnumCase $betterConstantOrEnumCase): mixed => $this->getConstantValue($betterConstantOrEnumCase),
211: $this->filterBetterReflectionClassConstants($filter),
212: );
213: }
214:
215: /**
216: * @deprecated Use getReflectionConstant()
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
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: public function newInstance(mixed ...$args): object
372: {
373: throw new Exception\NotImplemented('Not implemented');
374: }
375:
376: public function newInstanceWithoutConstructor(): object
377: {
378: throw new Exception\NotImplemented('Not implemented');
379: }
380:
381: public function newInstanceArgs(array|null $args = null): object
382: {
383: throw new Exception\NotImplemented('Not implemented');
384: }
385:
386: public function getParentClass(): ReflectionClass|false
387: {
388: return false;
389: }
390:
391: public function isSubclassOf(CoreReflectionClass|string $class): bool
392: {
393: $realParentClassNames = $this->betterReflectionEnum->getParentClassNames();
394:
395: $parentClassNames = array_combine(array_map(static fn (string $parentClassName): string => strtolower($parentClassName), $realParentClassNames), $realParentClassNames);
396:
397: $className = $class instanceof CoreReflectionClass ? $class->getName() : $class;
398: $lowercasedClassName = strtolower($className);
399:
400: $realParentClassName = $parentClassNames[$lowercasedClassName] ?? $className;
401:
402: if ($this->betterReflectionEnum->isSubclassOf($realParentClassName)) {
403: return true;
404: }
405:
406: return $this->implementsInterface($className);
407: }
408:
409: /**
410: * @return array<string, mixed>
411: *
412: * @psalm-suppress LessSpecificImplementedReturnType
413: */
414: public function getStaticProperties(): array
415: {
416: return $this->betterReflectionEnum->getStaticProperties();
417: }
418:
419: public function getStaticPropertyValue(string $name, mixed $default = null): mixed
420: {
421: throw new CoreReflectionException(sprintf('Property %s::$%s does not exist', $this->betterReflectionEnum->getName(), $name));
422: }
423:
424: public function setStaticPropertyValue(string $name, mixed $value): void
425: {
426: throw new CoreReflectionException(sprintf('Class %s does not have a property named %s', $this->betterReflectionEnum->getName(), $name));
427: }
428:
429: /** @return array<non-empty-string, mixed> */
430: public function getDefaultProperties(): array
431: {
432: return $this->betterReflectionEnum->getDefaultProperties();
433: }
434:
435: public function isIterateable(): bool
436: {
437: return $this->betterReflectionEnum->isIterateable();
438: }
439:
440: public function isIterable(): bool
441: {
442: return $this->isIterateable();
443: }
444:
445: public function implementsInterface(CoreReflectionClass|string $interface): bool
446: {
447: $realInterfaceNames = $this->betterReflectionEnum->getInterfaceNames();
448:
449: $interfaceNames = array_combine(array_map(static fn (string $interfaceName): string => strtolower($interfaceName), $realInterfaceNames), $realInterfaceNames);
450:
451: $interfaceName = $interface instanceof CoreReflectionClass ? $interface->getName() : $interface;
452: $lowercasedInterfaceName = strtolower($interfaceName);
453:
454: $realInterfaceName = $interfaceNames[$lowercasedInterfaceName] ?? $interfaceName;
455:
456: return $this->betterReflectionEnum->implementsInterface($realInterfaceName);
457: }
458:
459: public function getExtension(): ?CoreReflectionExtension
460: {
461: throw new Exception\NotImplemented('Not implemented');
462: }
463:
464: /** @return non-empty-string|false */
465: public function getExtensionName(): string|false
466: {
467: return $this->betterReflectionEnum->getExtensionName() ?? false;
468: }
469:
470: public function inNamespace(): bool
471: {
472: return $this->betterReflectionEnum->inNamespace();
473: }
474:
475: public function getNamespaceName(): string
476: {
477: return $this->betterReflectionEnum->getNamespaceName() ?? '';
478: }
479:
480: public function getShortName(): string
481: {
482: return $this->betterReflectionEnum->getShortName();
483: }
484:
485: /**
486: * @param class-string|null $name
487: *
488: * @return list<ReflectionAttribute|FakeReflectionAttribute>
489: */
490: public function getAttributes(string|null $name = null, int $flags = 0): array
491: {
492: if ($flags !== 0 && $flags !== ReflectionAttribute::IS_INSTANCEOF) {
493: throw new ValueError('Argument #2 ($flags) must be a valid attribute filter flag');
494: }
495:
496: if ($name !== null && $flags !== 0) {
497: $attributes = $this->betterReflectionEnum->getAttributesByInstance($name);
498: } elseif ($name !== null) {
499: $attributes = $this->betterReflectionEnum->getAttributesByName($name);
500: } else {
501: $attributes = $this->betterReflectionEnum->getAttributes();
502: }
503:
504: /** @psalm-suppress ImpureFunctionCall */
505: return array_map(static fn (BetterReflectionAttribute $betterReflectionAttribute): ReflectionAttribute|FakeReflectionAttribute => ReflectionAttributeFactory::create($betterReflectionAttribute), $attributes);
506: }
507:
508: public function isEnum(): bool
509: {
510: return $this->betterReflectionEnum->isEnum();
511: }
512:
513: public function hasCase(string $name): bool
514: {
515: if ($name === '') {
516: return false;
517: }
518:
519: return $this->betterReflectionEnum->hasCase($name);
520: }
521:
522: public function getCase(string $name): ReflectionEnumUnitCase|ReflectionEnumBackedCase
523: {
524: $case = $name !== '' ? $this->betterReflectionEnum->getCase($name) : null;
525:
526: if ($case === null) {
527: throw new CoreReflectionException(sprintf('Case %s::%s does not exist', $this->betterReflectionEnum->getName(), $name));
528: }
529:
530: if ($this->betterReflectionEnum->isBacked()) {
531: return new ReflectionEnumBackedCase($case);
532: }
533:
534: return new ReflectionEnumUnitCase($case);
535: }
536:
537: /** @return list<ReflectionEnumUnitCase|ReflectionEnumBackedCase> */
538: public function getCases(): array
539: {
540: /** @psalm-suppress ImpureFunctionCall */
541: return array_map(function (BetterReflectionEnumCase $case): ReflectionEnumUnitCase|ReflectionEnumBackedCase {
542: if ($this->betterReflectionEnum->isBacked()) {
543: return new ReflectionEnumBackedCase($case);
544: }
545:
546: return new ReflectionEnumUnitCase($case);
547: }, array_values($this->betterReflectionEnum->getCases()));
548: }
549:
550: public function isBacked(): bool
551: {
552: return $this->betterReflectionEnum->isBacked();
553: }
554:
555: public function getBackingType(): ReflectionNamedType|null
556: {
557: if ($this->betterReflectionEnum->isBacked()) {
558: return new ReflectionNamedType($this->betterReflectionEnum->getBackingType(), false);
559: }
560:
561: return null;
562: }
563: }
564: