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\EnumCase;
10: use PHPStan\BetterReflection\NodeCompiler\CompiledValue;
11: use PHPStan\BetterReflection\NodeCompiler\CompileNodeToValue;
12: use PHPStan\BetterReflection\NodeCompiler\CompilerContext;
13: use PHPStan\BetterReflection\Reflection\Attribute\ReflectionAttributeHelper;
14: use PHPStan\BetterReflection\Reflection\Deprecated\DeprecatedHelper;
15: use PHPStan\BetterReflection\Reflection\StringCast\ReflectionEnumCaseStringCast;
16: use PHPStan\BetterReflection\Reflector\Reflector;
17: use PHPStan\BetterReflection\Util\CalculateReflectionColumn;
18: use PHPStan\BetterReflection\Util\GetLastDocComment;
19:
20: use function assert;
21: use function is_int;
22: use function is_string;
23:
24: /** @psalm-immutable */
25: class ReflectionEnumCase
26: {
27: private Reflector $reflector;
28: private ReflectionEnum $enum;
29: /** @var non-empty-string */
30: private string $name;
31:
32: /**
33: * @var \PhpParser\Node\Expr|null
34: */
35: private $value;
36:
37: /** @var list<ReflectionAttribute> */
38: private array $attributes;
39:
40: /** @var non-empty-string|null */
41: private $docComment;
42:
43: /** @var positive-int */
44: private int $startLine;
45:
46: /** @var positive-int */
47: private int $endLine;
48:
49: /** @var positive-int */
50: private int $startColumn;
51:
52: /** @var positive-int */
53: private int $endColumn;
54:
55: /** @psalm-allow-private-mutation
56: * @var \PHPStan\BetterReflection\NodeCompiler\CompiledValue|null */
57: private $compiledValue = null;
58:
59: private function __construct(Reflector $reflector, EnumCase $node, ReflectionEnum $enum)
60: {
61: $this->reflector = $reflector;
62: $this->enum = $enum;
63: $this->name = $node->name->toString();
64: $this->value = $node->expr;
65: $this->attributes = ReflectionAttributeHelper::createAttributes($reflector, $this, $node->attrGroups);
66: $this->docComment = GetLastDocComment::forNode($node);
67: $startLine = $node->getStartLine();
68: assert($startLine > 0);
69: $endLine = $node->getEndLine();
70: assert($endLine > 0);
71: $this->startLine = $startLine;
72: $this->endLine = $endLine;
73: $this->startColumn = CalculateReflectionColumn::getStartColumn($this->enum->getLocatedSource()->getSource(), $node);
74: $this->endColumn = CalculateReflectionColumn::getEndColumn($this->enum->getLocatedSource()->getSource(), $node);
75: }
76:
77: /** @internal */
78: public static function createFromNode(Reflector $reflector, EnumCase $node, ReflectionEnum $enum): self
79: {
80: return new self($reflector, $node, $enum);
81: }
82:
83: /** @return non-empty-string */
84: public function getName(): string
85: {
86: return $this->name;
87: }
88:
89: /**
90: * Check ReflectionEnum::isBacked() being true first to avoid throwing exception.
91: *
92: * @throws LogicException
93: */
94: public function getValueExpression(): Node\Expr
95: {
96: if ($this->value === null) {
97: throw new LogicException('This enum case does not have a value');
98: }
99:
100: return $this->value;
101: }
102:
103: /**
104: * @return int|string
105: */
106: public function getValue()
107: {
108: $value = $this->getCompiledValue()->value;
109: assert(is_string($value) || is_int($value));
110:
111: return $value;
112: }
113:
114: /**
115: * Check ReflectionEnum::isBacked() being true first to avoid throwing exception.
116: *
117: * @throws LogicException
118: */
119: private function getCompiledValue(): CompiledValue
120: {
121: if ($this->value === null) {
122: throw new LogicException('This enum case does not have a value');
123: }
124:
125: if ($this->compiledValue === null) {
126: $this->compiledValue = (new CompileNodeToValue())->__invoke(
127: $this->value,
128: new CompilerContext($this->reflector, $this),
129: );
130: }
131:
132: return $this->compiledValue;
133: }
134:
135: /** @return positive-int */
136: public function getStartLine(): int
137: {
138: return $this->startLine;
139: }
140:
141: /** @return positive-int */
142: public function getEndLine(): int
143: {
144: return $this->endLine;
145: }
146:
147: /** @return positive-int */
148: public function getStartColumn(): int
149: {
150: return $this->startColumn;
151: }
152:
153: /** @return positive-int */
154: public function getEndColumn(): int
155: {
156: return $this->endColumn;
157: }
158:
159: public function getDeclaringEnum(): ReflectionEnum
160: {
161: return $this->enum;
162: }
163:
164: public function getDeclaringClass(): ReflectionClass
165: {
166: return $this->enum;
167: }
168:
169: /** @return non-empty-string|null */
170: public function getDocComment(): ?string
171: {
172: return $this->docComment;
173: }
174:
175: public function isDeprecated(): bool
176: {
177: return DeprecatedHelper::isDeprecated($this);
178: }
179:
180: /** @return list<ReflectionAttribute> */
181: public function getAttributes(): array
182: {
183: return $this->attributes;
184: }
185:
186: /** @return list<ReflectionAttribute> */
187: public function getAttributesByName(string $name): array
188: {
189: return ReflectionAttributeHelper::filterAttributesByName($this->getAttributes(), $name);
190: }
191:
192: /**
193: * @param class-string $className
194: *
195: * @return list<ReflectionAttribute>
196: */
197: public function getAttributesByInstance(string $className): array
198: {
199: return ReflectionAttributeHelper::filterAttributesByInstance($this->getAttributes(), $className);
200: }
201:
202: /** @return non-empty-string */
203: public function __toString(): string
204: {
205: return ReflectionEnumCaseStringCast::toString($this);
206: }
207: }
208: