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