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: class ReflectionConstant implements Reflection
30: {
31: /** @var non-empty-string */
32: private $name;
33:
34: /** @var non-empty-string */
35: private $shortName;
36:
37: /**
38: * @var \PhpParser\Node\Expr
39: */
40: private $value;
41:
42: /**
43: * @var string|null
44: */
45: private $docComment;
46:
47: /** @var positive-int */
48: private $startLine;
49:
50: /** @var positive-int */
51: private $endLine;
52:
53: /** @var positive-int */
54: private $startColumn;
55:
56: /** @var positive-int */
57: private $endColumn;
58:
59: /**
60: * @var \PHPStan\BetterReflection\NodeCompiler\CompiledValue|null
61: */
62: private $compiledValue = null;
63: /**
64: * @var \PHPStan\BetterReflection\Reflector\Reflector
65: */
66: private $reflector;
67: /**
68: * @var \PHPStan\BetterReflection\SourceLocator\Located\LocatedSource
69: */
70: private $locatedSource;
71: /**
72: * @var string|null
73: */
74: private $namespace = null;
75: /**
76: * @param \PhpParser\Node\Stmt\Const_|\PhpParser\Node\Expr\FuncCall $node
77: */
78: private function __construct(Reflector $reflector, $node, LocatedSource $locatedSource, ?string $namespace = null, ?int $positionInNode = null)
79: {
80: $this->reflector = $reflector;
81: $this->locatedSource = $locatedSource;
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: $startLine = $node->getStartLine();
94: assert($startLine > 0);
95: $endLine = $node->getEndLine();
96: assert($endLine > 0);
97: $this->startLine = $startLine;
98: $this->endLine = $endLine;
99: $this->startColumn = CalculateReflectionColumn::getStartColumn($this->locatedSource->getSource(), $node);
100: $this->endColumn = CalculateReflectionColumn::getEndColumn($this->locatedSource->getSource(), $node);
101: }
102:
103: /**
104: * Create a ReflectionConstant by name, using default reflectors etc.
105: *
106: * @deprecated Use Reflector instead.
107: *
108: * @throws IdentifierNotFound
109: */
110: public static function createFromName(string $constantName): self
111: {
112: return (new BetterReflection())->reflector()->reflectConstant($constantName);
113: }
114:
115: /**
116: * Create a reflection of a constant
117: *
118: * @internal
119: *
120: * @param Node\Stmt\Const_|Node\Expr\FuncCall $node Node has to be processed by the PhpParser\NodeVisitor\NameResolver
121: */
122: public static function createFromNode(Reflector $reflector, Node $node, LocatedSource $locatedSource, ?string $namespace = null, ?int $positionInNode = null): self
123: {
124: if ($node instanceof Node\Stmt\Const_) {
125: assert(is_int($positionInNode));
126:
127: return self::createFromConstKeyword($reflector, $node, $locatedSource, $namespace, $positionInNode);
128: }
129: return self::createFromDefineFunctionCall($reflector, $node, $locatedSource);
130: }
131:
132: private static function createFromConstKeyword(Reflector $reflector, Node\Stmt\Const_ $node, LocatedSource $locatedSource, ?string $namespace, int $positionInNode): self
133: {
134: return new self($reflector, $node, $locatedSource, $namespace, $positionInNode);
135: }
136:
137: /** @throws InvalidConstantNode */
138: private static function createFromDefineFunctionCall(Reflector $reflector, Node\Expr\FuncCall $node, LocatedSource $locatedSource): self
139: {
140: ConstantNodeChecker::assertValidDefineFunctionCall($node);
141: return new self($reflector, $node, $locatedSource);
142: }
143:
144: /**
145: * Get the "short" name of the constant (e.g. for A\B\FOO, this will return
146: * "FOO").
147: *
148: * @return non-empty-string
149: */
150: public function getShortName(): string
151: {
152: return $this->shortName;
153: }
154:
155: /**
156: * Get the "full" name of the constant (e.g. for A\B\FOO, this will return
157: * "A\B\FOO").
158: *
159: * @return non-empty-string
160: */
161: public function getName(): string
162: {
163: return $this->name;
164: }
165:
166: /**
167: * Get the "namespace" name of the constant (e.g. for A\B\FOO, this will
168: * return "A\B").
169: */
170: public function getNamespaceName(): ?string
171: {
172: return $this->namespace;
173: }
174:
175: /**
176: * Decide if this constant is part of a namespace. Returns false if the constant
177: * is in the global namespace or does not have a specified namespace.
178: */
179: public function inNamespace(): bool
180: {
181: return $this->namespace !== null;
182: }
183:
184: public function getExtensionName(): ?string
185: {
186: return $this->locatedSource->getExtensionName();
187: }
188:
189: /**
190: * Is this an internal constant?
191: */
192: public function isInternal(): bool
193: {
194: return $this->locatedSource->isInternal();
195: }
196:
197: /**
198: * Is this a user-defined function (will always return the opposite of
199: * whatever isInternal returns).
200: */
201: public function isUserDefined(): bool
202: {
203: return ! $this->isInternal();
204: }
205:
206: public function isDeprecated(): bool
207: {
208: return AnnotationHelper::isDeprecated($this->getDocComment());
209: }
210:
211: /**
212: * @deprecated Use getValueExpression()
213: * @return Node\Expr
214: */
215: public function getValueExpr(): Node\Expr
216: {
217: return $this->getValueExpression();
218: }
219:
220: public function getValueExpression(): Node\Expr
221: {
222: return $this->value;
223: }
224:
225: /**
226: * @deprecated Use getValueExpression()
227: * @return mixed
228: */
229: public function getValue()
230: {
231: if ($this->compiledValue === null) {
232: $this->compiledValue = (new CompileNodeToValue())->__invoke($this->value, new CompilerContext($this->reflector, $this));
233: }
234:
235: return $this->compiledValue->value;
236: }
237:
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: /**
281: * Returns the doc comment for this constant
282: */
283: public function getDocComment(): ?string
284: {
285: return $this->docComment;
286: }
287:
288: public function __toString(): string
289: {
290: return ReflectionConstantStringCast::toString($this);
291: }
292:
293: /**
294: * @param \PhpParser\Node\Stmt\Const_|\PhpParser\Node\Expr\FuncCall $node
295: */
296: private function setNamesFromNode($node, ?int $positionInNode): void
297: {
298: if ($node instanceof Node\Expr\FuncCall) {
299: $name = $this->getNameFromDefineFunctionCall($node);
300:
301: $nameParts = explode('\\', $name);
302: $this->namespace = implode('\\', array_slice($nameParts, 0, -1)) ?: null;
303:
304: $shortName = $nameParts[count($nameParts) - 1];
305: assert($shortName !== '');
306: } else {
307: /** @psalm-suppress PossiblyNullArrayOffset */
308: $constNode = $node->consts[$positionInNode];
309: $namespacedName = $constNode->namespacedName;
310: assert($namespacedName instanceof Node\Name);
311:
312: $name = $namespacedName->toString();
313: assert($name !== '');
314: $shortName = $constNode->name->name;
315: assert($shortName !== '');
316: }
317:
318: $this->name = $name;
319: $this->shortName = $shortName;
320: }
321:
322: /** @return non-empty-string */
323: private function getNameFromDefineFunctionCall(Node\Expr\FuncCall $node): string
324: {
325: $argumentNameNode = $node->args[0];
326: assert($argumentNameNode instanceof Node\Arg);
327: $nameNode = $argumentNameNode->value;
328: assert($nameNode instanceof Node\Scalar\String_);
329:
330: /** @psalm-var non-empty-string */
331: return $nameNode->value;
332: }
333: }
334: