1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace PHPStan\BetterReflection\Reflection;
6:
7: use LogicException;
8: use PhpParser\Node;
9: use PhpParser\Node\Stmt\Class_ as ClassNode;
10: use PhpParser\Node\Stmt\Enum_ as EnumNode;
11: use PhpParser\Node\Stmt\Interface_ as InterfaceNode;
12: use PhpParser\Node\Stmt\Trait_ as TraitNode;
13: use PHPStan\BetterReflection\Reflector\Reflector;
14: use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource;
15:
16: use function array_combine;
17: use function array_filter;
18: use function array_key_exists;
19: use function array_map;
20: use function assert;
21:
22: /** @psalm-immutable */
23: class ReflectionEnum extends ReflectionClass
24: {
25: private Reflector $reflector;
26: /**
27: * @var \PHPStan\BetterReflection\Reflection\ReflectionNamedType|null
28: */
29: private $backingType;
30:
31: /** @var array<non-empty-string, ReflectionEnumCase> */
32: private array $cases;
33:
34: /**
35: * @param non-empty-string|null $namespace
36: *
37: * @phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod.Found
38: */
39: private function __construct(Reflector $reflector, EnumNode $node, LocatedSource $locatedSource, ?string $namespace = null)
40: {
41: $this->reflector = $reflector;
42: parent::__construct($reflector, $node, $locatedSource, $namespace);
43: $this->backingType = $this->createBackingType($node);
44: $this->cases = $this->createCases($node);
45: }
46:
47: /**
48: * @internal
49: *
50: * @param non-empty-string|null $namespace
51: * @param ClassNode|InterfaceNode|TraitNode|EnumNode $node
52: */
53: public static function createFromNode(Reflector $reflector, $node, LocatedSource $locatedSource, ?string $namespace = null): self
54: {
55: assert($node instanceof EnumNode);
56: return new self($reflector, $node, $locatedSource, $namespace);
57: }
58:
59: /** @param non-empty-string $name */
60: public function hasCase(string $name): bool
61: {
62: return array_key_exists($name, $this->cases);
63: }
64:
65: /** @param non-empty-string $name */
66: public function getCase(string $name): ?\PHPStan\BetterReflection\Reflection\ReflectionEnumCase
67: {
68: return $this->cases[$name] ?? null;
69: }
70:
71: /** @return array<non-empty-string, ReflectionEnumCase> */
72: public function getCases(): array
73: {
74: return $this->cases;
75: }
76:
77: /** @return array<non-empty-string, ReflectionEnumCase> */
78: private function createCases(EnumNode $node): array
79: {
80: $enumCasesNodes = array_filter($node->stmts, static fn (Node\Stmt $stmt): bool => $stmt instanceof Node\Stmt\EnumCase);
81:
82: return array_combine(
83: array_map(static fn (Node\Stmt\EnumCase $enumCaseNode): string => $enumCaseNode->name->toString(), $enumCasesNodes),
84: array_map(fn (Node\Stmt\EnumCase $enumCaseNode): ReflectionEnumCase => ReflectionEnumCase::createFromNode($this->reflector, $enumCaseNode, $this), $enumCasesNodes),
85: );
86: }
87:
88: public function isBacked(): bool
89: {
90: return $this->backingType !== null;
91: }
92:
93: public function getBackingType(): ReflectionNamedType
94: {
95: if ($this->backingType === null) {
96: throw new LogicException('This enum does not have a backing type available');
97: }
98:
99: return $this->backingType;
100: }
101:
102: private function createBackingType(EnumNode $node): ?\PHPStan\BetterReflection\Reflection\ReflectionNamedType
103: {
104: if ($node->scalarType === null) {
105: return null;
106: }
107:
108: $backingType = ReflectionNamedType::createFromNode($this->reflector, $this, $node->scalarType);
109: assert($backingType instanceof ReflectionNamedType);
110:
111: return $backingType;
112: }
113: }
114: