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