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: /**
108: * Create a ReflectionConstant by name, using default reflectors etc.
109: *
110: * @deprecated Use Reflector instead.
111: *
112: * @throws IdentifierNotFound
113: */
114: public static function createFromName(string $constantName): self
115: {
116: return (new BetterReflection())->reflector()->reflectConstant($constantName);
117: }
118:
119: /**
120: * Create a reflection of a constant
121: *
122: * @internal
123: *
124: * @param Node\Stmt\Const_|Node\Expr\FuncCall $node Node has to be processed by the PhpParser\NodeVisitor\NameResolver
125: * @param non-empty-string|null $namespace
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: /** @param non-empty-string|null $namespace */
138: private static function createFromConstKeyword(Reflector $reflector, Node\Stmt\Const_ $node, LocatedSource $locatedSource, ?string $namespace, int $positionInNode): self
139: {
140: return new self($reflector, $node, $locatedSource, $namespace, $positionInNode);
141: }
142:
143: /** @throws InvalidConstantNode */
144: private static function createFromDefineFunctionCall(Reflector $reflector, Node\Expr\FuncCall $node, LocatedSource $locatedSource): self
145: {
146: ConstantNodeChecker::assertValidDefineFunctionCall($node);
147: return new self($reflector, $node, $locatedSource);
148: }
149:
150: /**
151: * Get the "short" name of the constant (e.g. for A\B\FOO, this will return
152: * "FOO").
153: *
154: * @return non-empty-string
155: */
156: public function getShortName(): string
157: {
158: return $this->shortName;
159: }
160:
161: /**
162: * Get the "full" name of the constant (e.g. for A\B\FOO, this will return
163: * "A\B\FOO").
164: *
165: * @return non-empty-string
166: */
167: public function getName(): string
168: {
169: return $this->name;
170: }
171:
172: /**
173: * Get the "namespace" name of the constant (e.g. for A\B\FOO, this will
174: * return "A\B").
175: *
176: * @return non-empty-string|null
177: */
178: public function getNamespaceName(): ?string
179: {
180: return $this->namespace;
181: }
182:
183: /**
184: * Decide if this constant is part of a namespace. Returns false if the constant
185: * is in the global namespace or does not have a specified namespace.
186: */
187: public function inNamespace(): bool
188: {
189: return $this->namespace !== null;
190: }
191:
192: /** @return non-empty-string|null */
193: public function getExtensionName(): ?string
194: {
195: return $this->locatedSource->getExtensionName();
196: }
197:
198: /**
199: * Is this an internal constant?
200: */
201: public function isInternal(): bool
202: {
203: return $this->locatedSource->isInternal();
204: }
205:
206: /**
207: * Is this a user-defined function (will always return the opposite of
208: * whatever isInternal returns).
209: */
210: public function isUserDefined(): bool
211: {
212: return ! $this->isInternal();
213: }
214:
215: public function isDeprecated(): bool
216: {
217: return AnnotationHelper::isDeprecated($this->getDocComment());
218: }
219:
220: /**
221: * @deprecated Use getValueExpression()
222: * @return Node\Expr
223: */
224: public function getValueExpr(): Node\Expr
225: {
226: return $this->getValueExpression();
227: }
228:
229: public function getValueExpression(): Node\Expr
230: {
231: return $this->value;
232: }
233:
234: /**
235: * @deprecated Use getValueExpression()
236: * @return mixed
237: */
238: public function getValue()
239: {
240: if ($this->compiledValue === null) {
241: $this->compiledValue = (new CompileNodeToValue())->__invoke($this->value, new CompilerContext($this->reflector, $this));
242: }
243:
244: return $this->compiledValue->value;
245: }
246:
247: /** @return non-empty-string|null */
248: public function getFileName(): ?string
249: {
250: return $this->locatedSource->getFileName();
251: }
252:
253: public function getLocatedSource(): LocatedSource
254: {
255: return $this->locatedSource;
256: }
257:
258: /**
259: * Get the line number that this constant starts on.
260: *
261: * @return positive-int
262: */
263: public function getStartLine(): int
264: {
265: return $this->startLine;
266: }
267:
268: /**
269: * Get the line number that this constant ends on.
270: *
271: * @return positive-int
272: */
273: public function getEndLine(): int
274: {
275: return $this->endLine;
276: }
277:
278: /** @return positive-int */
279: public function getStartColumn(): int
280: {
281: return $this->startColumn;
282: }
283:
284: /** @return positive-int */
285: public function getEndColumn(): int
286: {
287: return $this->endColumn;
288: }
289:
290: /** @return non-empty-string|null */
291: public function getDocComment(): ?string
292: {
293: return $this->docComment;
294: }
295:
296: /** @return non-empty-string */
297: public function __toString(): string
298: {
299: return ReflectionConstantStringCast::toString($this);
300: }
301:
302: /**
303: * @param \PhpParser\Node\Stmt\Const_|\PhpParser\Node\Expr\FuncCall $node
304: */
305: private function setNamesFromNode($node, ?int $positionInNode): void
306: {
307: if ($node instanceof Node\Expr\FuncCall) {
308: $name = $this->getNameFromDefineFunctionCall($node);
309:
310: $nameParts = explode('\\', $name);
311: $this->namespace = implode('\\', array_slice($nameParts, 0, -1)) ?: null;
312:
313: $shortName = $nameParts[count($nameParts) - 1];
314: assert($shortName !== '');
315: } else {
316: /** @psalm-suppress PossiblyNullArrayOffset */
317: $constNode = $node->consts[$positionInNode];
318: $namespacedName = $constNode->namespacedName;
319: assert($namespacedName instanceof Node\Name);
320:
321: $name = $namespacedName->toString();
322: assert($name !== '');
323: $shortName = $constNode->name->name;
324: assert($shortName !== '');
325: }
326:
327: $this->name = $name;
328: $this->shortName = $shortName;
329: }
330:
331: /** @return non-empty-string */
332: private function getNameFromDefineFunctionCall(Node\Expr\FuncCall $node): string
333: {
334: $argumentNameNode = $node->args[0];
335: assert($argumentNameNode instanceof Node\Arg);
336: $nameNode = $argumentNameNode->value;
337: assert($nameNode instanceof Node\Scalar\String_);
338:
339: /** @psalm-var non-empty-string */
340: return $nameNode->value;
341: }
342: }
343: