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