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: /**
26: * @var \PHPStan\BetterReflection\Reflection\ReflectionNamedType|null
27: */
28: private $backingType;
29:
30: /** @var array<non-empty-string, ReflectionEnumCase> */
31: private $cases;
32: /**
33: * @var \PHPStan\BetterReflection\Reflector\Reflector
34: */
35: private $reflector;
36: /**
37: * @param non-empty-string|null $namespace
38: *
39: * @phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod.Found
40: */
41: private function __construct(Reflector $reflector, EnumNode $node, LocatedSource $locatedSource, ?string $namespace = null)
42: {
43: $this->reflector = $reflector;
44: parent::__construct($reflector, $node, $locatedSource, $namespace);
45: $this->backingType = $this->createBackingType($node);
46: $this->cases = $this->createCases($node);
47: }
48: /**
49: * @internal
50: *
51: * @param EnumNode $node
52: * @param non-empty-string|null $namespace
53: *
54: * @psalm-suppress MoreSpecificImplementedParamType
55: * @return $this
56: */
57: public static function createFromNode(Reflector $reflector, $node, LocatedSource $locatedSource, ?string $namespace = null): \PHPStan\BetterReflection\Reflection\ReflectionClass
58: {
59: $node = $node;
60: assert($node instanceof EnumNode);
61: return new self($reflector, $node, $locatedSource, $namespace);
62: }
63:
64: /** @param non-empty-string $name */
65: public function hasCase(string $name): bool
66: {
67: return array_key_exists($name, $this->cases);
68: }
69:
70: /** @param non-empty-string $name */
71: public function getCase(string $name): ?\PHPStan\BetterReflection\Reflection\ReflectionEnumCase
72: {
73: return $this->cases[$name] ?? null;
74: }
75:
76: /** @return array<non-empty-string, ReflectionEnumCase> */
77: public function getCases(): array
78: {
79: return $this->cases;
80: }
81:
82: /** @return array<non-empty-string, ReflectionEnumCase> */
83: private function createCases(EnumNode $node): array
84: {
85: $enumCasesNodes = array_filter($node->stmts, static function (Node\Stmt $stmt) : bool {
86: return $stmt instanceof Node\Stmt\EnumCase;
87: });
88:
89: return array_combine(array_map(static function (Node\Stmt\EnumCase $enumCaseNode): string {
90: $enumCaseName = $enumCaseNode->name->toString();
91: assert($enumCaseName !== '');
92:
93: return $enumCaseName;
94: }, $enumCasesNodes), array_map(function (Node\Stmt\EnumCase $enumCaseNode) : ReflectionEnumCase {
95: return ReflectionEnumCase::createFromNode($this->reflector, $enumCaseNode, $this);
96: }, $enumCasesNodes));
97: }
98:
99: public function isBacked(): bool
100: {
101: return $this->backingType !== null;
102: }
103:
104: public function getBackingType(): ReflectionNamedType
105: {
106: if ($this->backingType === null) {
107: throw new LogicException('This enum does not have a backing type available');
108: }
109:
110: return $this->backingType;
111: }
112:
113: private function createBackingType(EnumNode $node): ?\PHPStan\BetterReflection\Reflection\ReflectionNamedType
114: {
115: if ($node->scalarType === null) {
116: return null;
117: }
118:
119: $backingType = ReflectionNamedType::createFromNode($this->reflector, $this, $node->scalarType);
120: assert($backingType instanceof ReflectionNamedType);
121:
122: return $backingType;
123: }
124: }
125: