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: * While ReflectionEnum::isBacked() is a sufficient check when working with valid PHP code,
91: * with an invalid enum declaration we can still encounter a back enum case with a missing value.
92: */
93: public function hasValueExpression(): bool
94: {
95: return $this->value !== null;
96: }
97:
98: /**
99: * Check self::hasValueExpression() being true first to avoid throwing exception.
100: *
101: * @throws LogicException
102: */
103: public function getValueExpression(): Node\Expr
104: {
105: if ($this->value === null) {
106: throw new LogicException('This enum case does not have a value');
107: }
108:
109: return $this->value;
110: }
111:
112: /**
113: * @return int|string
114: */
115: public function getValue()
116: {
117: $value = $this->getCompiledValue()->value;
118: assert(is_string($value) || is_int($value));
119:
120: return $value;
121: }
122:
123: /**
124: * Check self::hasValueExpression() being true first to avoid throwing exception.
125: *
126: * @throws LogicException
127: */
128: private function getCompiledValue(): CompiledValue
129: {
130: if ($this->value === null) {
131: throw new LogicException('This enum case does not have a value');
132: }
133:
134: if ($this->compiledValue === null) {
135: $this->compiledValue = (new CompileNodeToValue())->__invoke(
136: $this->value,
137: new CompilerContext($this->reflector, $this),
138: );
139: }
140:
141: return $this->compiledValue;
142: }
143:
144: /** @return positive-int */
145: public function getStartLine(): int
146: {
147: return $this->startLine;
148: }
149:
150: /** @return positive-int */
151: public function getEndLine(): int
152: {
153: return $this->endLine;
154: }
155:
156: /** @return positive-int */
157: public function getStartColumn(): int
158: {
159: return $this->startColumn;
160: }
161:
162: /** @return positive-int */
163: public function getEndColumn(): int
164: {
165: return $this->endColumn;
166: }
167:
168: public function getDeclaringEnum(): ReflectionEnum
169: {
170: return $this->enum;
171: }
172:
173: public function getDeclaringClass(): ReflectionClass
174: {
175: return $this->enum;
176: }
177:
178: /** @return non-empty-string|null */
179: public function getDocComment(): ?string
180: {
181: return $this->docComment;
182: }
183:
184: public function isDeprecated(): bool
185: {
186: return DeprecatedHelper::isDeprecated($this);
187: }
188:
189: /** @return list<ReflectionAttribute> */
190: public function getAttributes(): array
191: {
192: return $this->attributes;
193: }
194:
195: /** @return list<ReflectionAttribute> */
196: public function getAttributesByName(string $name): array
197: {
198: return ReflectionAttributeHelper::filterAttributesByName($this->getAttributes(), $name);
199: }
200:
201: /**
202: * @param class-string $className
203: *
204: * @return list<ReflectionAttribute>
205: */
206: public function getAttributesByInstance(string $className): array
207: {
208: return ReflectionAttributeHelper::filterAttributesByInstance($this->getAttributes(), $className);
209: }
210:
211: /** @return non-empty-string */
212: public function __toString(): string
213: {
214: return ReflectionEnumCaseStringCast::toString($this);
215: }
216: }
217: