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