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 string|null
77: */
78: private $namespace = null;
79: /**
80: * @param \PhpParser\Node\Stmt\Const_|\PhpParser\Node\Expr\FuncCall $node
81: */
82: private function __construct(Reflector $reflector, $node, LocatedSource $locatedSource, ?string $namespace = null, ?int $positionInNode = null)
83: {
84: $this->reflector = $reflector;
85: $this->locatedSource = $locatedSource;
86: /** @psalm-allow-private-mutation */
87: $this->namespace = $namespace;
88: $this->setNamesFromNode($node, $positionInNode);
89: if ($node instanceof Node\Expr\FuncCall) {
90: $argumentValueNode = $node->args[1];
91: assert($argumentValueNode instanceof Node\Arg);
92: $this->value = $argumentValueNode->value;
93: } else {
94: /** @psalm-suppress PossiblyNullArrayOffset */
95: $this->value = $node->consts[$positionInNode]->value;
96: }
97: $this->docComment = GetLastDocComment::forNode($node);
98: $startLine = $node->getStartLine();
99: assert($startLine > 0);
100: $endLine = $node->getEndLine();
101: assert($endLine > 0);
102: $this->startLine = $startLine;
103: $this->endLine = $endLine;
104: $this->startColumn = CalculateReflectionColumn::getStartColumn($this->locatedSource->getSource(), $node);
105: $this->endColumn = CalculateReflectionColumn::getEndColumn($this->locatedSource->getSource(), $node);
106: }
107:
108: /**
109: * Create a ReflectionConstant by name, using default reflectors etc.
110: *
111: * @deprecated Use Reflector instead.
112: *
113: * @throws IdentifierNotFound
114: */
115: public static function createFromName(string $constantName): self
116: {
117: return (new BetterReflection())->reflector()->reflectConstant($constantName);
118: }
119:
120: /**
121: * Create a reflection of a constant
122: *
123: * @internal
124: *
125: * @param Node\Stmt\Const_|Node\Expr\FuncCall $node Node has to be processed by the PhpParser\NodeVisitor\NameResolver
126: */
127: public static function createFromNode(Reflector $reflector, Node $node, LocatedSource $locatedSource, ?string $namespace = null, ?int $positionInNode = null): self
128: {
129: if ($node instanceof Node\Stmt\Const_) {
130: assert(is_int($positionInNode));
131:
132: return self::createFromConstKeyword($reflector, $node, $locatedSource, $namespace, $positionInNode);
133: }
134: return self::createFromDefineFunctionCall($reflector, $node, $locatedSource);
135: }
136:
137: private static function createFromConstKeyword(Reflector $reflector, Node\Stmt\Const_ $node, LocatedSource $locatedSource, ?string $namespace, int $positionInNode): self
138: {
139: return new self($reflector, $node, $locatedSource, $namespace, $positionInNode);
140: }
141:
142: /** @throws InvalidConstantNode */
143: private static function createFromDefineFunctionCall(Reflector $reflector, Node\Expr\FuncCall $node, LocatedSource $locatedSource): self
144: {
145: ConstantNodeChecker::assertValidDefineFunctionCall($node);
146: return new self($reflector, $node, $locatedSource);
147: }
148:
149: /**
150: * Get the "short" name of the constant (e.g. for A\B\FOO, this will return
151: * "FOO").
152: *
153: * @return non-empty-string
154: */
155: public function getShortName(): string
156: {
157: return $this->shortName;
158: }
159:
160: /**
161: * Get the "full" name of the constant (e.g. for A\B\FOO, this will return
162: * "A\B\FOO").
163: *
164: * @return non-empty-string
165: */
166: public function getName(): string
167: {
168: return $this->name;
169: }
170:
171: /**
172: * Get the "namespace" name of the constant (e.g. for A\B\FOO, this will
173: * return "A\B").
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 AnnotationHelper::isDeprecated($this->getDocComment());
215: }
216:
217: /**
218: * @deprecated Use getValueExpression()
219: * @return Node\Expr
220: */
221: public function getValueExpr(): Node\Expr
222: {
223: return $this->getValueExpression();
224: }
225:
226: public function getValueExpression(): Node\Expr
227: {
228: return $this->value;
229: }
230:
231: /**
232: * @deprecated Use getValueExpression()
233: * @return mixed
234: */
235: public function getValue()
236: {
237: if ($this->compiledValue === null) {
238: $this->compiledValue = (new CompileNodeToValue())->__invoke($this->value, new CompilerContext($this->reflector, $this));
239: }
240:
241: return $this->compiledValue->value;
242: }
243:
244: /** @return non-empty-string|null */
245: public function getFileName(): ?string
246: {
247: return $this->locatedSource->getFileName();
248: }
249:
250: public function getLocatedSource(): LocatedSource
251: {
252: return $this->locatedSource;
253: }
254:
255: /**
256: * Get the line number that this constant starts on.
257: *
258: * @return positive-int
259: */
260: public function getStartLine(): int
261: {
262: return $this->startLine;
263: }
264:
265: /**
266: * Get the line number that this constant ends on.
267: *
268: * @return positive-int
269: */
270: public function getEndLine(): int
271: {
272: return $this->endLine;
273: }
274:
275: /** @return positive-int */
276: public function getStartColumn(): int
277: {
278: return $this->startColumn;
279: }
280:
281: /** @return positive-int */
282: public function getEndColumn(): int
283: {
284: return $this->endColumn;
285: }
286:
287: /** @return non-empty-string|null */
288: public function getDocComment(): ?string
289: {
290: return $this->docComment;
291: }
292:
293: /** @return non-empty-string */
294: public function __toString(): string
295: {
296: return ReflectionConstantStringCast::toString($this);
297: }
298:
299: /**
300: * @param \PhpParser\Node\Stmt\Const_|\PhpParser\Node\Expr\FuncCall $node
301: */
302: private function setNamesFromNode($node, ?int $positionInNode): void
303: {
304: if ($node instanceof Node\Expr\FuncCall) {
305: $name = $this->getNameFromDefineFunctionCall($node);
306:
307: $nameParts = explode('\\', $name);
308: $this->namespace = implode('\\', array_slice($nameParts, 0, -1)) ?: null;
309:
310: $shortName = $nameParts[count($nameParts) - 1];
311: assert($shortName !== '');
312: } else {
313: /** @psalm-suppress PossiblyNullArrayOffset */
314: $constNode = $node->consts[$positionInNode];
315: $namespacedName = $constNode->namespacedName;
316: assert($namespacedName instanceof Node\Name);
317:
318: $name = $namespacedName->toString();
319: assert($name !== '');
320: $shortName = $constNode->name->name;
321: assert($shortName !== '');
322: }
323:
324: $this->name = $name;
325: $this->shortName = $shortName;
326: }
327:
328: /** @return non-empty-string */
329: private function getNameFromDefineFunctionCall(Node\Expr\FuncCall $node): string
330: {
331: $argumentNameNode = $node->args[0];
332: assert($argumentNameNode instanceof Node\Arg);
333: $nameNode = $argumentNameNode->value;
334: assert($nameNode instanceof Node\Scalar\String_);
335:
336: /** @psalm-var non-empty-string */
337: return $nameNode->value;
338: }
339: }
340: