1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace PHPStan\BetterReflection\Reflection;
6:
7: use PhpParser\Node;
8: use PHPStan\BetterReflection\BetterReflection;
9: use PHPStan\BetterReflection\NodeCompiler\CompiledValue;
10: use PHPStan\BetterReflection\NodeCompiler\CompileNodeToValue;
11: use PHPStan\BetterReflection\NodeCompiler\CompilerContext;
12: use PHPStan\BetterReflection\Reflection\Attribute\ReflectionAttributeHelper;
13: use PHPStan\BetterReflection\Reflection\Deprecated\DeprecatedHelper;
14: use PHPStan\BetterReflection\Reflection\Exception\InvalidConstantNode;
15: use PHPStan\BetterReflection\Reflection\StringCast\ReflectionConstantStringCast;
16: use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound;
17: use PHPStan\BetterReflection\Reflector\Reflector;
18: use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource;
19: use PHPStan\BetterReflection\Util\CalculateReflectionColumn;
20: use PHPStan\BetterReflection\Util\ConstantNodeChecker;
21: use PHPStan\BetterReflection\Util\GetLastDocComment;
22:
23: use function array_slice;
24: use function assert;
25: use function count;
26: use function explode;
27: use function implode;
28: use function is_int;
29:
30: /** @psalm-immutable */
31: class ReflectionConstant implements Reflection
32: {
33: private Reflector $reflector;
34: private LocatedSource $locatedSource;
35: /**
36: * @var non-empty-string|null
37: */
38: private $namespace = null;
39: /**
40: * @var non-empty-string
41: * @psalm-allow-private-mutation
42: */
43: private string $name;
44:
45: /**
46: * @var non-empty-string
47: * @psalm-allow-private-mutation
48: */
49: private string $shortName;
50:
51: private Node\Expr $value;
52:
53: /** @var non-empty-string|null */
54: private $docComment;
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: /** @var list<ReflectionAttribute> */
69: private array $attributes;
70:
71: /** @psalm-allow-private-mutation
72: * @var \PHPStan\BetterReflection\NodeCompiler\CompiledValue|null */
73: private $compiledValue = null;
74:
75: /** @param non-empty-string|null $namespace
76: * @param \PhpParser\Node\Stmt\Const_|\PhpParser\Node\Expr\FuncCall $node */
77: private function __construct(Reflector $reflector, $node, LocatedSource $locatedSource, ?string $namespace = null, ?int $positionInNode = null)
78: {
79: $this->reflector = $reflector;
80: $this->locatedSource = $locatedSource;
81: /** @psalm-allow-private-mutation */
82: $this->namespace = $namespace;
83: $this->setNamesFromNode($node, $positionInNode);
84: if ($node instanceof Node\Expr\FuncCall) {
85: $argumentValueNode = $node->args[1];
86: assert($argumentValueNode instanceof Node\Arg);
87: $this->value = $argumentValueNode->value;
88: } else {
89: /** @psalm-suppress PossiblyNullArrayOffset */
90: $this->value = $node->consts[$positionInNode]->value;
91: }
92: $this->docComment = GetLastDocComment::forNode($node);
93: $this->attributes = $node instanceof Node\Stmt\Const_
94: ? ReflectionAttributeHelper::createAttributes($reflector, $this, $node->attrGroups)
95: : [];
96: $startLine = $node->getStartLine();
97: assert($startLine > 0);
98: $endLine = $node->getEndLine();
99: assert($endLine > 0);
100: $this->startLine = $startLine;
101: $this->endLine = $endLine;
102: $this->startColumn = CalculateReflectionColumn::getStartColumn($this->locatedSource->getSource(), $node);
103: $this->endColumn = CalculateReflectionColumn::getEndColumn($this->locatedSource->getSource(), $node);
104: }
105:
106: /**
107: * Create a reflection of a constant
108: *
109: * @internal
110: *
111: * @param Node\Stmt\Const_|Node\Expr\FuncCall $node Node has to be processed by the PhpParser\NodeVisitor\NameResolver
112: * @param non-empty-string|null $namespace
113: */
114: public static function createFromNode(Reflector $reflector, Node $node, LocatedSource $locatedSource, ?string $namespace = null, ?int $positionInNode = null): self
115: {
116: if ($node instanceof Node\Stmt\Const_) {
117: assert(is_int($positionInNode));
118:
119: return self::createFromConstKeyword($reflector, $node, $locatedSource, $namespace, $positionInNode);
120: }
121: return self::createFromDefineFunctionCall($reflector, $node, $locatedSource);
122: }
123:
124: /** @param non-empty-string|null $namespace */
125: private static function createFromConstKeyword(Reflector $reflector, Node\Stmt\Const_ $node, LocatedSource $locatedSource, ?string $namespace, int $positionInNode): self
126: {
127: return new self(
128: $reflector,
129: $node,
130: $locatedSource,
131: $namespace,
132: $positionInNode,
133: );
134: }
135:
136: /** @throws InvalidConstantNode */
137: private static function createFromDefineFunctionCall(Reflector $reflector, Node\Expr\FuncCall $node, LocatedSource $locatedSource): self
138: {
139: ConstantNodeChecker::assertValidDefineFunctionCall($node);
140: return new self(
141: $reflector,
142: $node,
143: $locatedSource,
144: );
145: }
146:
147: /**
148: * Get the "short" name of the constant (e.g. for A\B\FOO, this will return
149: * "FOO").
150: *
151: * @return non-empty-string
152: */
153: public function getShortName(): string
154: {
155: return $this->shortName;
156: }
157:
158: /**
159: * Get the "full" name of the constant (e.g. for A\B\FOO, this will return
160: * "A\B\FOO").
161: *
162: * @return non-empty-string
163: */
164: public function getName(): string
165: {
166: return $this->name;
167: }
168:
169: /**
170: * Get the "namespace" name of the constant (e.g. for A\B\FOO, this will
171: * return "A\B").
172: *
173: * @return non-empty-string|null
174: */
175: public function getNamespaceName(): ?string
176: {
177: return $this->namespace;
178: }
179:
180: /**
181: * Decide if this constant is part of a namespace. Returns false if the constant
182: * is in the global namespace or does not have a specified namespace.
183: */
184: public function inNamespace(): bool
185: {
186: return $this->namespace !== null;
187: }
188:
189: /** @return non-empty-string|null */
190: public function getExtensionName(): ?string
191: {
192: return $this->locatedSource->getExtensionName();
193: }
194:
195: /**
196: * Is this an internal constant?
197: */
198: public function isInternal(): bool
199: {
200: return $this->locatedSource->isInternal();
201: }
202:
203: /**
204: * Is this a user-defined function (will always return the opposite of
205: * whatever isInternal returns).
206: */
207: public function isUserDefined(): bool
208: {
209: return ! $this->isInternal();
210: }
211:
212: public function isDeprecated(): bool
213: {
214: return DeprecatedHelper::isDeprecated($this);
215: }
216:
217: public function getValueExpression(): Node\Expr
218: {
219: return $this->value;
220: }
221:
222: /**
223: * @return mixed
224: */
225: public function getValue()
226: {
227: if ($this->compiledValue === null) {
228: $this->compiledValue = (new CompileNodeToValue())->__invoke(
229: $this->value,
230: new CompilerContext($this->reflector, $this),
231: );
232: }
233:
234: return $this->compiledValue->value;
235: }
236:
237: /** @return non-empty-string|null */
238: public function getFileName(): ?string
239: {
240: return $this->locatedSource->getFileName();
241: }
242:
243: public function getLocatedSource(): LocatedSource
244: {
245: return $this->locatedSource;
246: }
247:
248: /**
249: * Get the line number that this constant starts on.
250: *
251: * @return positive-int
252: */
253: public function getStartLine(): int
254: {
255: return $this->startLine;
256: }
257:
258: /**
259: * Get the line number that this constant ends on.
260: *
261: * @return positive-int
262: */
263: public function getEndLine(): int
264: {
265: return $this->endLine;
266: }
267:
268: /** @return positive-int */
269: public function getStartColumn(): int
270: {
271: return $this->startColumn;
272: }
273:
274: /** @return positive-int */
275: public function getEndColumn(): int
276: {
277: return $this->endColumn;
278: }
279:
280: /** @return non-empty-string|null */
281: public function getDocComment(): ?string
282: {
283: return $this->docComment;
284: }
285:
286: /** @return non-empty-string */
287: public function __toString(): string
288: {
289: return ReflectionConstantStringCast::toString($this);
290: }
291:
292: /** @return list<ReflectionAttribute> */
293: public function getAttributes(): array
294: {
295: return $this->attributes;
296: }
297:
298: /** @return list<ReflectionAttribute> */
299: public function getAttributesByName(string $name): array
300: {
301: return ReflectionAttributeHelper::filterAttributesByName($this->getAttributes(), $name);
302: }
303:
304: /**
305: * @param class-string $className
306: *
307: * @return list<ReflectionAttribute>
308: */
309: public function getAttributesByInstance(string $className): array
310: {
311: return ReflectionAttributeHelper::filterAttributesByInstance($this->getAttributes(), $className);
312: }
313:
314: /**
315: * @param \PhpParser\Node\Stmt\Const_|\PhpParser\Node\Expr\FuncCall $node
316: */
317: private function setNamesFromNode($node, ?int $positionInNode): void
318: {
319: if ($node instanceof Node\Expr\FuncCall) {
320: $name = $this->getNameFromDefineFunctionCall($node);
321:
322: $nameParts = explode('\\', $name);
323: $this->namespace = implode('\\', array_slice($nameParts, 0, -1)) ?: null;
324:
325: $shortName = $nameParts[count($nameParts) - 1];
326: assert($shortName !== '');
327: } else {
328: /** @psalm-suppress PossiblyNullArrayOffset */
329: $constNode = $node->consts[$positionInNode];
330: $namespacedName = $constNode->namespacedName;
331: assert($namespacedName instanceof Node\Name);
332:
333: $name = $namespacedName->toString();
334: $shortName = $constNode->name->name;
335: }
336:
337: /** @phpstan-ignore assign.readOnlyPropertyByPhpDoc */
338: $this->name = $name;
339: /** @phpstan-ignore assign.readOnlyPropertyByPhpDoc */
340: $this->shortName = $shortName;
341: }
342:
343: /** @return non-empty-string */
344: private function getNameFromDefineFunctionCall(Node\Expr\FuncCall $node): string
345: {
346: $argumentNameNode = $node->args[0];
347: assert($argumentNameNode instanceof Node\Arg);
348: $nameNode = $argumentNameNode->value;
349: assert($nameNode instanceof Node\Scalar\String_);
350:
351: /** @psalm-var non-empty-string */
352: return $nameNode->value;
353: }
354: }
355: