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(
40: Reflector $reflector,
41: EnumNode $node,
42: LocatedSource $locatedSource,
43: ?string $namespace = null
44: ) {
45: $this->reflector = $reflector;
46: parent::__construct($reflector, $node, $locatedSource, $namespace);
47:
48: $this->backingType = $this->createBackingType($node);
49: $this->cases = $this->createCases($node);
50: }
51:
52: /**
53: * @return array<string, mixed>
54: */
55: public function exportToCache(): array
56: {
57: return array_merge(parent::exportToCache(), [
58: 'backingType' => ($nullsafeVariable1 = $this->backingType) ? $nullsafeVariable1->exportToCache() : null,
59: 'cases' => array_map(
60: static fn (ReflectionEnumCase $case) => $case->exportToCache(),
61: $this->cases,
62: ),
63: ]);
64: }
65:
66: /**
67: * @param array<string, mixed> $data
68: * @return static
69: */
70: public static function importFromCache(Reflector $reflector, array $data): self
71: {
72: $ref = parent::importFromCache($reflector, $data);
73: $ref->backingType = $data['backingType'] !== null
74: ? ReflectionNamedType::importFromCache($reflector, $data['backingType'], $ref)
75: : null;
76: $ref->cases = array_map(
77: static fn ($caseData) => ReflectionEnumCase::importFromCache($reflector, $caseData, $ref),
78: $data['cases'],
79: );
80:
81: return $ref;
82: }
83:
84: /**
85: * @internal
86: *
87: * @param non-empty-string|null $namespace
88: * @param ClassNode|InterfaceNode|TraitNode|EnumNode $node
89: */
90: public static function createFromNode(
91: Reflector $reflector,
92: $node,
93: LocatedSource $locatedSource,
94: ?string $namespace = null
95: ): self {
96: assert($node instanceof EnumNode);
97:
98: return new self($reflector, $node, $locatedSource, $namespace);
99: }
100:
101: /** @param non-empty-string $name */
102: public function hasCase(string $name): bool
103: {
104: return array_key_exists($name, $this->cases);
105: }
106:
107: /** @param non-empty-string $name */
108: public function getCase(string $name): ?\PHPStan\BetterReflection\Reflection\ReflectionEnumCase
109: {
110: return $this->cases[$name] ?? null;
111: }
112:
113: /** @return array<non-empty-string, ReflectionEnumCase> */
114: public function getCases(): array
115: {
116: return $this->cases;
117: }
118:
119: /** @return array<non-empty-string, ReflectionEnumCase> */
120: private function createCases(EnumNode $node): array
121: {
122: $enumCasesNodes = array_filter($node->stmts, static fn (Node\Stmt $stmt): bool => $stmt instanceof Node\Stmt\EnumCase);
123:
124: return array_combine(
125: array_map(static fn (Node\Stmt\EnumCase $enumCaseNode): string => $enumCaseNode->name->toString(), $enumCasesNodes),
126: array_map(fn (Node\Stmt\EnumCase $enumCaseNode): ReflectionEnumCase => ReflectionEnumCase::createFromNode($this->reflector, $enumCaseNode, $this), $enumCasesNodes),
127: );
128: }
129:
130: public function isBacked(): bool
131: {
132: return $this->backingType !== null;
133: }
134:
135: public function getBackingType(): ReflectionNamedType
136: {
137: if ($this->backingType === null) {
138: throw new LogicException('This enum does not have a backing type available');
139: }
140:
141: return $this->backingType;
142: }
143:
144: private function createBackingType(EnumNode $node): ?\PHPStan\BetterReflection\Reflection\ReflectionNamedType
145: {
146: if ($node->scalarType === null) {
147: return null;
148: }
149:
150: $backingType = ReflectionNamedType::createFromNode($this->reflector, $this, $node->scalarType);
151: assert($backingType instanceof ReflectionNamedType);
152:
153: return $backingType;
154: }
155: }
156: