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