1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace PHPStan\BetterReflection\Reflection;
6:
7: use PhpParser\Node;
8: use PhpParser\Node\Stmt\ClassConst;
9: use ReflectionClassConstant as CoreReflectionClassConstant;
10: use PHPStan\BetterReflection\BetterReflection;
11: use PHPStan\BetterReflection\NodeCompiler\CompiledValue;
12: use PHPStan\BetterReflection\NodeCompiler\CompileNodeToValue;
13: use PHPStan\BetterReflection\NodeCompiler\CompilerContext;
14: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClassConstant as ReflectionClassConstantAdapter;
15: use PHPStan\BetterReflection\Reflection\Attribute\ReflectionAttributeHelper;
16: use PHPStan\BetterReflection\Reflection\Deprecated\DeprecatedHelper;
17: use PHPStan\BetterReflection\Reflection\StringCast\ReflectionClassConstantStringCast;
18: use PHPStan\BetterReflection\Reflector\Reflector;
19: use PHPStan\BetterReflection\Util\CalculateReflectionColumn;
20: use PHPStan\BetterReflection\Util\GetLastDocComment;
21:
22: use function array_map;
23: use function assert;
24:
25: /** @psalm-immutable */
26: class ReflectionClassConstant
27: {
28: private Reflector $reflector;
29: private ReflectionClass $declaringClass;
30: private ReflectionClass $implementingClass;
31: /** @var non-empty-string */
32: private string $name;
33:
34: /** @var int-mask-of<ReflectionClassConstantAdapter::IS_*> */
35: private int $modifiers;
36:
37: /**
38: * @var \PHPStan\BetterReflection\Reflection\ReflectionNamedType|\PHPStan\BetterReflection\Reflection\ReflectionUnionType|\PHPStan\BetterReflection\Reflection\ReflectionIntersectionType|null
39: */
40: private $type;
41:
42: private Node\Expr $value;
43:
44: /** @var non-empty-string|null */
45: private $docComment;
46:
47: /** @var list<ReflectionAttribute> */
48: private array $attributes;
49:
50: /** @var positive-int */
51: private int $startLine;
52:
53: /** @var positive-int */
54: private int $endLine;
55:
56: /** @var positive-int */
57: private int $startColumn;
58:
59: /** @var positive-int */
60: private int $endColumn;
61:
62: /** @psalm-allow-private-mutation
63: * @var \PHPStan\BetterReflection\NodeCompiler\CompiledValue|null */
64: private $compiledValue = null;
65:
66: private function __construct(Reflector $reflector, ClassConst $node, int $positionInNode, ReflectionClass $declaringClass, ReflectionClass $implementingClass)
67: {
68: $this->reflector = $reflector;
69: $this->declaringClass = $declaringClass;
70: $this->implementingClass = $implementingClass;
71: $this->name = $node->consts[$positionInNode]->name->name;
72: $this->modifiers = $this->computeModifiers($node);
73: $this->type = $this->createType($node);
74: $this->value = $node->consts[$positionInNode]->value;
75: $this->docComment = GetLastDocComment::forNode($node);
76: $this->attributes = ReflectionAttributeHelper::createAttributes($reflector, $this, $node->attrGroups);
77: $startLine = $node->getStartLine();
78: assert($startLine > 0);
79: $endLine = $node->getEndLine();
80: assert($endLine > 0);
81: $this->startLine = $startLine;
82: $this->endLine = $endLine;
83: $this->startColumn = CalculateReflectionColumn::getStartColumn($declaringClass->getLocatedSource()->getSource(), $node);
84: $this->endColumn = CalculateReflectionColumn::getEndColumn($declaringClass->getLocatedSource()->getSource(), $node);
85: }
86:
87: /**
88: * Create a reflection of a class's constant by Const Node
89: *
90: * @internal
91: */
92: public static function createFromNode(Reflector $reflector, ClassConst $node, int $positionInNode, ReflectionClass $declaringClass, ReflectionClass $implementingClass): self
93: {
94: return new self(
95: $reflector,
96: $node,
97: $positionInNode,
98: $declaringClass,
99: $implementingClass,
100: );
101: }
102:
103: /** @internal */
104: public function withImplementingClass(ReflectionClass $implementingClass): self
105: {
106: $clone = clone $this;
107: $clone->implementingClass = $implementingClass;
108:
109: $clone->attributes = array_map(static fn (ReflectionAttribute $attribute): ReflectionAttribute => $attribute->withOwner($clone), $this->attributes);
110:
111: $this->compiledValue = null;
112:
113: return $clone;
114: }
115:
116: /**
117: * Get the name of the reflection (e.g. if this is a ReflectionClass this
118: * will be the class name).
119: *
120: * @return non-empty-string
121: */
122: public function getName(): string
123: {
124: return $this->name;
125: }
126:
127: /**
128: * @return \PHPStan\BetterReflection\Reflection\ReflectionNamedType|\PHPStan\BetterReflection\Reflection\ReflectionUnionType|\PHPStan\BetterReflection\Reflection\ReflectionIntersectionType|null
129: */
130: private function createType(ClassConst $node)
131: {
132: $type = $node->type;
133:
134: if ($type === null) {
135: return null;
136: }
137:
138: assert($type instanceof Node\Identifier || $type instanceof Node\Name || $type instanceof Node\NullableType || $type instanceof Node\UnionType || $type instanceof Node\IntersectionType);
139:
140: return ReflectionType::createFromNode($this->reflector, $this, $type);
141: }
142:
143: /**
144: * @return \PHPStan\BetterReflection\Reflection\ReflectionNamedType|\PHPStan\BetterReflection\Reflection\ReflectionUnionType|\PHPStan\BetterReflection\Reflection\ReflectionIntersectionType|null
145: */
146: public function getType()
147: {
148: return $this->type;
149: }
150:
151: public function hasType(): bool
152: {
153: return $this->type !== null;
154: }
155:
156: public function getValueExpression(): Node\Expr
157: {
158: return $this->value;
159: }
160:
161: /**
162: * Returns constant value
163: * @return mixed
164: */
165: public function getValue()
166: {
167: if ($this->compiledValue === null) {
168: $this->compiledValue = (new CompileNodeToValue())->__invoke(
169: $this->value,
170: new CompilerContext($this->reflector, $this),
171: );
172: }
173:
174: return $this->compiledValue->value;
175: }
176:
177: /**
178: * Constant is public
179: */
180: public function isPublic(): bool
181: {
182: return (bool) ($this->modifiers & ReflectionClassConstantAdapter::IS_PUBLIC_COMPATIBILITY);
183: }
184:
185: /**
186: * Constant is private
187: */
188: public function isPrivate(): bool
189: {
190: // Private constant cannot be final
191: return $this->modifiers === ReflectionClassConstantAdapter::IS_PRIVATE_COMPATIBILITY;
192: }
193:
194: /**
195: * Constant is protected
196: */
197: public function isProtected(): bool
198: {
199: return (bool) ($this->modifiers & ReflectionClassConstantAdapter::IS_PROTECTED_COMPATIBILITY);
200: }
201:
202: public function isFinal(): bool
203: {
204: $final = (bool) ($this->modifiers & ReflectionClassConstantAdapter::IS_FINAL_COMPATIBILITY);
205: if ($final) {
206: return true;
207: }
208:
209: if (BetterReflection::$phpVersion >= 80100) {
210: return false;
211: }
212:
213: return $this->getDeclaringClass()->isInterface();
214: }
215:
216: /**
217: * Returns a bitfield of the access modifiers for this constant
218: *
219: * @return int-mask-of<ReflectionClassConstantAdapter::IS_*>
220: */
221: public function getModifiers(): int
222: {
223: return $this->modifiers;
224: }
225:
226: /**
227: * Get the line number that this constant starts on.
228: *
229: * @return positive-int
230: */
231: public function getStartLine(): int
232: {
233: return $this->startLine;
234: }
235:
236: /**
237: * Get the line number that this constant ends on.
238: *
239: * @return positive-int
240: */
241: public function getEndLine(): int
242: {
243: return $this->endLine;
244: }
245:
246: /** @return positive-int */
247: public function getStartColumn(): int
248: {
249: return $this->startColumn;
250: }
251:
252: /** @return positive-int */
253: public function getEndColumn(): int
254: {
255: return $this->endColumn;
256: }
257:
258: /**
259: * Get the declaring class
260: */
261: public function getDeclaringClass(): ReflectionClass
262: {
263: return $this->declaringClass;
264: }
265:
266: /**
267: * Get the class that implemented the method based on trait use.
268: */
269: public function getImplementingClass(): ReflectionClass
270: {
271: return $this->implementingClass;
272: }
273:
274: /** @return non-empty-string|null */
275: public function getDocComment(): ?string
276: {
277: return $this->docComment;
278: }
279:
280: public function isDeprecated(): bool
281: {
282: return DeprecatedHelper::isDeprecated($this);
283: }
284:
285: /** @return non-empty-string */
286: public function __toString(): string
287: {
288: return ReflectionClassConstantStringCast::toString($this);
289: }
290:
291: /** @return list<ReflectionAttribute> */
292: public function getAttributes(): array
293: {
294: return $this->attributes;
295: }
296:
297: /** @return list<ReflectionAttribute> */
298: public function getAttributesByName(string $name): array
299: {
300: return ReflectionAttributeHelper::filterAttributesByName($this->getAttributes(), $name);
301: }
302:
303: /**
304: * @param class-string $className
305: *
306: * @return list<ReflectionAttribute>
307: */
308: public function getAttributesByInstance(string $className): array
309: {
310: return ReflectionAttributeHelper::filterAttributesByInstance($this->getAttributes(), $className);
311: }
312:
313: /** @return int-mask-of<ReflectionClassConstantAdapter::IS_*> */
314: private function computeModifiers(ClassConst $node): int
315: {
316: $modifiers = $node->isFinal() ? ReflectionClassConstantAdapter::IS_FINAL_COMPATIBILITY : 0;
317: $modifiers += $node->isPrivate() ? ReflectionClassConstantAdapter::IS_PRIVATE_COMPATIBILITY : 0;
318: $modifiers += $node->isProtected() ? ReflectionClassConstantAdapter::IS_PROTECTED_COMPATIBILITY : 0;
319: $modifiers += $node->isPublic() ? ReflectionClassConstantAdapter::IS_PUBLIC_COMPATIBILITY : 0;
320:
321: return $modifiers;
322: }
323: }
324: