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