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