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