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