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): self
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: return $this->betterReflectionEnum->isSubclassOf($realParentClassName) || $this->implementsInterface($className);
403: }
404:
405: /**
406: * @return array<string, mixed>
407: *
408: * @psalm-suppress LessSpecificImplementedReturnType
409: */
410: public function getStaticProperties(): array
411: {
412: return $this->betterReflectionEnum->getStaticProperties();
413: }
414:
415: public function getStaticPropertyValue(string $name, mixed $default = null): mixed
416: {
417: throw new CoreReflectionException(sprintf('Property %s::$%s does not exist', $this->betterReflectionEnum->getName(), $name));
418: }
419:
420: public function setStaticPropertyValue(string $name, mixed $value): void
421: {
422: throw new CoreReflectionException(sprintf('Class %s does not have a property named %s', $this->betterReflectionEnum->getName(), $name));
423: }
424:
425: /** @return array<non-empty-string, mixed> */
426: public function getDefaultProperties(): array
427: {
428: return $this->betterReflectionEnum->getDefaultProperties();
429: }
430:
431: public function isIterateable(): bool
432: {
433: return $this->betterReflectionEnum->isIterateable();
434: }
435:
436: public function isIterable(): bool
437: {
438: return $this->isIterateable();
439: }
440:
441: public function implementsInterface(CoreReflectionClass|string $interface): bool
442: {
443: $realInterfaceNames = $this->betterReflectionEnum->getInterfaceNames();
444:
445: $interfaceNames = array_combine(array_map(static fn (string $interfaceName): string => strtolower($interfaceName), $realInterfaceNames), $realInterfaceNames);
446:
447: $interfaceName = $interface instanceof CoreReflectionClass ? $interface->getName() : $interface;
448: $lowercasedIntefaceName = strtolower($interfaceName);
449:
450: $realInterfaceName = $interfaceNames[$lowercasedIntefaceName] ?? $interfaceName;
451:
452: return $this->betterReflectionEnum->implementsInterface($realInterfaceName);
453: }
454:
455: public function getExtension(): ?CoreReflectionExtension
456: {
457: throw new Exception\NotImplemented('Not implemented');
458: }
459:
460: /** @return non-empty-string|false */
461: public function getExtensionName(): string|false
462: {
463: return $this->betterReflectionEnum->getExtensionName() ?? false;
464: }
465:
466: public function inNamespace(): bool
467: {
468: return $this->betterReflectionEnum->inNamespace();
469: }
470:
471: public function getNamespaceName(): string
472: {
473: return $this->betterReflectionEnum->getNamespaceName() ?? '';
474: }
475:
476: public function getShortName(): string
477: {
478: return $this->betterReflectionEnum->getShortName();
479: }
480:
481: /**
482: * @param class-string|null $name
483: *
484: * @return list<ReflectionAttribute|FakeReflectionAttribute>
485: */
486: public function getAttributes(string|null $name = null, int $flags = 0): array
487: {
488: if ($flags !== 0 && $flags !== ReflectionAttribute::IS_INSTANCEOF) {
489: throw new ValueError('Argument #2 ($flags) must be a valid attribute filter flag');
490: }
491:
492: if ($name !== null && $flags !== 0) {
493: $attributes = $this->betterReflectionEnum->getAttributesByInstance($name);
494: } elseif ($name !== null) {
495: $attributes = $this->betterReflectionEnum->getAttributesByName($name);
496: } else {
497: $attributes = $this->betterReflectionEnum->getAttributes();
498: }
499:
500: /** @psalm-suppress ImpureFunctionCall */
501: return array_map(static fn (BetterReflectionAttribute $betterReflectionAttribute): ReflectionAttribute|FakeReflectionAttribute => ReflectionAttributeFactory::create($betterReflectionAttribute), $attributes);
502: }
503:
504: public function isEnum(): bool
505: {
506: return $this->betterReflectionEnum->isEnum();
507: }
508:
509: public function hasCase(string $name): bool
510: {
511: if ($name === '') {
512: return false;
513: }
514:
515: return $this->betterReflectionEnum->hasCase($name);
516: }
517:
518: public function getCase(string $name): ReflectionEnumUnitCase|ReflectionEnumBackedCase
519: {
520: $case = $name !== '' ? $this->betterReflectionEnum->getCase($name) : null;
521:
522: if ($case === null) {
523: throw new CoreReflectionException(sprintf('Case %s::%s does not exist', $this->betterReflectionEnum->getName(), $name));
524: }
525:
526: if ($this->betterReflectionEnum->isBacked()) {
527: return new ReflectionEnumBackedCase($case);
528: }
529:
530: return new ReflectionEnumUnitCase($case);
531: }
532:
533: /** @return list<ReflectionEnumUnitCase|ReflectionEnumBackedCase> */
534: public function getCases(): array
535: {
536: /** @psalm-suppress ImpureFunctionCall */
537: return array_map(function (BetterReflectionEnumCase $case): ReflectionEnumUnitCase|ReflectionEnumBackedCase {
538: if ($this->betterReflectionEnum->isBacked()) {
539: return new ReflectionEnumBackedCase($case);
540: }
541:
542: return new ReflectionEnumUnitCase($case);
543: }, array_values($this->betterReflectionEnum->getCases()));
544: }
545:
546: public function isBacked(): bool
547: {
548: return $this->betterReflectionEnum->isBacked();
549: }
550:
551: public function getBackingType(): ReflectionNamedType|null
552: {
553: if ($this->betterReflectionEnum->isBacked()) {
554: return new ReflectionNamedType($this->betterReflectionEnum->getBackingType(), false);
555: }
556:
557: return null;
558: }
559: }
560: