These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Parser Reflection API |
||
4 | * |
||
5 | * @copyright Copyright 2015, Lisachenko Alexander <[email protected]> |
||
6 | * |
||
7 | * This source file is subject to the license that is bundled |
||
8 | * with this source code in the file LICENSE. |
||
9 | */ |
||
10 | |||
11 | namespace Go\ParserReflection\ValueResolver; |
||
12 | |||
13 | use Go\ParserReflection\ReflectionClass; |
||
14 | use Go\ParserReflection\ReflectionException; |
||
15 | use Go\ParserReflection\ReflectionFileNamespace; |
||
16 | use PhpParser\Node; |
||
17 | use PhpParser\Node\Expr; |
||
18 | use PhpParser\Node\Scalar; |
||
19 | use PhpParser\Node\Scalar\MagicConst; |
||
20 | |||
21 | /** |
||
22 | * Tries to resolve expression into value |
||
23 | */ |
||
24 | class NodeExpressionResolver |
||
25 | { |
||
26 | |||
27 | /** |
||
28 | * List of exception for constant fetch |
||
29 | * |
||
30 | * @var array |
||
31 | */ |
||
32 | private static $notConstants = [ |
||
33 | 'true' => true, |
||
34 | 'false' => true, |
||
35 | 'null' => true, |
||
36 | ]; |
||
37 | |||
38 | /** |
||
39 | * Name of the constant (if present) |
||
40 | * |
||
41 | * @var null|string |
||
42 | */ |
||
43 | private $constantName = null; |
||
44 | |||
45 | /** |
||
46 | * Current reflection context for parsing |
||
47 | * |
||
48 | * @var mixed|\Go\ParserReflection\ReflectionClass |
||
49 | */ |
||
50 | private $context; |
||
51 | |||
52 | /** |
||
53 | * Flag if expression is constant |
||
54 | * |
||
55 | * @var bool |
||
56 | */ |
||
57 | private $isConstant = false; |
||
58 | |||
59 | /** |
||
60 | * Node resolving level, 1 = top-level |
||
61 | * |
||
62 | * @var int |
||
63 | */ |
||
64 | private $nodeLevel = 0; |
||
65 | |||
66 | /** |
||
67 | * @var mixed Value of expression/constant |
||
68 | */ |
||
69 | private $value; |
||
70 | |||
71 | 69 | public function __construct($context) |
|
72 | { |
||
73 | 69 | $this->context = $context; |
|
74 | 69 | } |
|
75 | |||
76 | 19 | public function getConstantName() |
|
77 | { |
||
78 | 19 | return $this->constantName; |
|
79 | } |
||
80 | |||
81 | 46 | public function getValue() |
|
82 | { |
||
83 | 46 | return $this->value; |
|
84 | } |
||
85 | |||
86 | 19 | public function isConstant() |
|
87 | { |
||
88 | 19 | return $this->isConstant; |
|
89 | } |
||
90 | |||
91 | /** |
||
92 | * {@inheritDoc} |
||
93 | */ |
||
94 | 46 | public function process(Node $node) |
|
95 | { |
||
96 | 46 | $this->nodeLevel = 0; |
|
97 | 46 | $this->isConstant = false; |
|
98 | 46 | $this->constantName = null; |
|
99 | 46 | $this->value = $this->resolve($node); |
|
100 | 46 | } |
|
101 | |||
102 | /** |
||
103 | * Resolves node into valid value |
||
104 | * |
||
105 | * @param Node $node |
||
106 | * |
||
107 | * @return mixed |
||
108 | */ |
||
109 | 46 | protected function resolve(Node $node) |
|
110 | { |
||
111 | 46 | $value = null; |
|
112 | try { |
||
113 | 46 | ++$this->nodeLevel; |
|
114 | |||
115 | 46 | $nodeType = $node->getType(); |
|
116 | 46 | $methodName = 'resolve' . str_replace('_', '', $nodeType); |
|
117 | 46 | if (method_exists($this, $methodName)) { |
|
118 | 46 | $value = $this->$methodName($node); |
|
119 | } |
||
120 | 46 | } finally { |
|
121 | 46 | --$this->nodeLevel; |
|
122 | } |
||
123 | |||
124 | 46 | return $value; |
|
125 | } |
||
126 | |||
127 | 9 | protected function resolveScalarDNumber(Scalar\DNumber $node) |
|
128 | { |
||
129 | 9 | return $node->value; |
|
130 | } |
||
131 | |||
132 | 27 | protected function resolveScalarLNumber(Scalar\LNumber $node) |
|
133 | { |
||
134 | 27 | return $node->value; |
|
135 | } |
||
136 | |||
137 | 22 | protected function resolveScalarString(Scalar\String_ $node) |
|
138 | { |
||
139 | 22 | return $node->value; |
|
140 | } |
||
141 | |||
142 | protected function resolveScalarMagicConstMethod() |
||
143 | { |
||
144 | if ($this->context instanceof \ReflectionMethod) { |
||
145 | $fullName = $this->context->getDeclaringClass()->getName() . '::' . $this->context->getShortName(); |
||
146 | |||
147 | return $fullName; |
||
148 | } |
||
149 | |||
150 | return ''; |
||
151 | } |
||
152 | |||
153 | 1 | protected function resolveScalarMagicConstFunction() |
|
154 | { |
||
155 | 1 | if ($this->context instanceof \ReflectionFunctionAbstract) { |
|
156 | 1 | return $this->context->getName(); |
|
157 | } |
||
158 | |||
159 | return ''; |
||
160 | } |
||
161 | |||
162 | 28 | protected function resolveScalarMagicConstNamespace() |
|
163 | { |
||
164 | 28 | if (method_exists($this->context, 'getNamespaceName')) { |
|
165 | 24 | return $this->context->getNamespaceName(); |
|
166 | } |
||
167 | |||
168 | 4 | if ($this->context instanceof ReflectionFileNamespace) { |
|
169 | 4 | return $this->context->getName(); |
|
170 | } |
||
171 | |||
172 | return ''; |
||
173 | } |
||
174 | |||
175 | 10 | protected function resolveScalarMagicConstClass() |
|
176 | { |
||
177 | 10 | if ($this->context instanceof \ReflectionClass) { |
|
178 | 4 | return $this->context->getName(); |
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
179 | } |
||
180 | 6 | if (method_exists($this->context, 'getDeclaringClass')) { |
|
181 | $declaringClass = $this->context->getDeclaringClass(); |
||
182 | if ($declaringClass instanceof \ReflectionClass) { |
||
183 | return $declaringClass->getName(); |
||
0 ignored issues
–
show
Consider using
$declaringClass->name . There is an issue with getName() and APC-enabled PHP versions.
Loading history...
|
|||
184 | } |
||
185 | } |
||
186 | |||
187 | 6 | return ''; |
|
188 | } |
||
189 | |||
190 | 3 | protected function resolveScalarMagicConstDir() |
|
191 | { |
||
192 | 3 | if (method_exists($this->context, 'getFileName')) { |
|
193 | 3 | return dirname($this->context->getFileName()); |
|
194 | } |
||
195 | |||
196 | return ''; |
||
197 | } |
||
198 | |||
199 | 6 | protected function resolveScalarMagicConstFile() |
|
200 | { |
||
201 | 6 | if (method_exists($this->context, 'getFileName')) { |
|
202 | 6 | return $this->context->getFileName(); |
|
203 | } |
||
204 | |||
205 | return ''; |
||
206 | } |
||
207 | |||
208 | 6 | protected function resolveScalarMagicConstLine(MagicConst\Line $node) |
|
209 | { |
||
210 | 6 | return $node->hasAttribute('startLine') ? $node->getAttribute('startLine') : 0; |
|
211 | } |
||
212 | |||
213 | 2 | protected function resolveScalarMagicConstTrait() |
|
214 | { |
||
215 | 2 | if ($this->context instanceof \ReflectionClass && $this->context->isTrait()) { |
|
216 | 2 | return $this->context->getName(); |
|
0 ignored issues
–
show
Loading history...
|
|||
217 | } |
||
218 | |||
219 | return ''; |
||
220 | } |
||
221 | |||
222 | 25 | protected function resolveExprConstFetch(Expr\ConstFetch $node) |
|
223 | { |
||
224 | 25 | $constantValue = null; |
|
225 | 25 | $isResolved = false; |
|
226 | |||
227 | /** @var ReflectionFileNamespace|null $fileNamespace */ |
||
228 | 25 | $fileNamespace = null; |
|
229 | 25 | $isFQNConstant = $node->name instanceof Node\Name\FullyQualified; |
|
230 | 25 | $constantName = $node->name->toString(); |
|
231 | |||
232 | 25 | if (!$isFQNConstant) { |
|
233 | 22 | if (method_exists($this->context, 'getFileName')) { |
|
234 | 22 | $fileName = $this->context->getFileName(); |
|
235 | 22 | $namespaceName = $this->resolveScalarMagicConstNamespace(); |
|
236 | 22 | $fileNamespace = new ReflectionFileNamespace($fileName, $namespaceName); |
|
237 | 22 | if ($fileNamespace->hasConstant($constantName)) { |
|
238 | 12 | $constantValue = $fileNamespace->getConstant($constantName); |
|
239 | 12 | $constantName = $fileNamespace->getName() . '\\' . $constantName; |
|
240 | 12 | $isResolved = true; |
|
241 | } |
||
242 | } |
||
243 | } |
||
244 | |||
245 | 25 | if (!$isResolved && defined($constantName)) { |
|
246 | 23 | $constantValue = constant($constantName); |
|
247 | } |
||
248 | |||
249 | 25 | if ($this->nodeLevel === 1 && !isset(self::$notConstants[$constantName])) { |
|
250 | 15 | $this->isConstant = true; |
|
251 | 15 | $this->constantName = $constantName; |
|
252 | } |
||
253 | |||
254 | 25 | return $constantValue; |
|
255 | } |
||
256 | |||
257 | 11 | protected function resolveExprClassConstFetch(Expr\ClassConstFetch $node) |
|
258 | { |
||
259 | 11 | $refClass = $this->fetchReflectionClass($node->class); |
|
0 ignored issues
–
show
It seems like
$node->class can also be of type object<PhpParser\Node\Expr> ; however, Go\ParserReflection\Valu...:fetchReflectionClass() does only seem to accept object<PhpParser\Node\Name> , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.
Loading history...
|
|||
260 | 11 | $constantName = $node->name; |
|
261 | |||
262 | // special handling of ::class constants |
||
263 | 11 | if ('class' === $constantName) { |
|
264 | 3 | return $refClass->getName(); |
|
265 | } |
||
266 | |||
267 | 9 | $this->isConstant = true; |
|
268 | 9 | $this->constantName = (string)$node->class . '::' . $constantName; |
|
269 | |||
270 | 9 | return $refClass->getConstant($constantName); |
|
271 | } |
||
272 | |||
273 | 9 | protected function resolveExprArray(Expr\Array_ $node) |
|
274 | { |
||
275 | 9 | $result = []; |
|
276 | 9 | foreach ($node->items as $itemIndex => $arrayItem) { |
|
277 | 8 | $itemValue = $this->resolve($arrayItem->value); |
|
278 | 8 | $itemKey = isset($arrayItem->key) ? $this->resolve($arrayItem->key) : $itemIndex; |
|
279 | 8 | $result[$itemKey] = $itemValue; |
|
280 | } |
||
281 | |||
282 | 9 | return $result; |
|
283 | } |
||
284 | |||
285 | 1 | protected function resolveExprBinaryOpPlus(Expr\BinaryOp\Plus $node) |
|
286 | { |
||
287 | 1 | return $this->resolve($node->left) + $this->resolve($node->right); |
|
288 | } |
||
289 | |||
290 | 1 | protected function resolveExprBinaryOpMinus(Expr\BinaryOp\Minus $node) |
|
291 | { |
||
292 | 1 | return $this->resolve($node->left) - $this->resolve($node->right); |
|
293 | } |
||
294 | |||
295 | 2 | protected function resolveExprBinaryOpMul(Expr\BinaryOp\Mul $node) |
|
296 | { |
||
297 | 2 | return $this->resolve($node->left) * $this->resolve($node->right); |
|
298 | } |
||
299 | |||
300 | 1 | protected function resolveExprBinaryOpPow(Expr\BinaryOp\Pow $node) |
|
301 | { |
||
302 | 1 | return pow($this->resolve($node->left), $this->resolve($node->right)); |
|
303 | } |
||
304 | |||
305 | 1 | protected function resolveExprBinaryOpDiv(Expr\BinaryOp\Div $node) |
|
306 | { |
||
307 | 1 | return $this->resolve($node->left) / $this->resolve($node->right); |
|
308 | } |
||
309 | |||
310 | 1 | protected function resolveExprBinaryOpMod(Expr\BinaryOp\Mod $node) |
|
311 | { |
||
312 | 1 | return $this->resolve($node->left) % $this->resolve($node->right); |
|
313 | } |
||
314 | |||
315 | 1 | protected function resolveExprBooleanNot(Expr\BooleanNot $node) |
|
316 | { |
||
317 | 1 | return !$this->resolve($node->expr); |
|
318 | } |
||
319 | |||
320 | 1 | protected function resolveExprBitwiseNot(Expr\BitwiseNot $node) |
|
321 | { |
||
322 | 1 | return ~$this->resolve($node->expr); |
|
323 | } |
||
324 | |||
325 | 1 | protected function resolveExprBinaryOpBitwiseOr(Expr\BinaryOp\BitwiseOr $node) |
|
326 | { |
||
327 | 1 | return $this->resolve($node->left) | $this->resolve($node->right); |
|
328 | } |
||
329 | |||
330 | 1 | protected function resolveExprBinaryOpBitwiseAnd(Expr\BinaryOp\BitwiseAnd $node) |
|
331 | { |
||
332 | 1 | return $this->resolve($node->left) & $this->resolve($node->right); |
|
333 | } |
||
334 | |||
335 | 1 | protected function resolveExprBinaryOpBitwiseXor(Expr\BinaryOp\BitwiseXor $node) |
|
336 | { |
||
337 | 1 | return $this->resolve($node->left) ^ $this->resolve($node->right); |
|
338 | } |
||
339 | |||
340 | 1 | protected function resolveExprBinaryOpShiftLeft(Expr\BinaryOp\ShiftLeft $node) |
|
341 | { |
||
342 | 1 | return $this->resolve($node->left) << $this->resolve($node->right); |
|
343 | } |
||
344 | |||
345 | 1 | protected function resolveExprBinaryOpShiftRight(Expr\BinaryOp\ShiftRight $node) |
|
346 | { |
||
347 | 1 | return $this->resolve($node->left) >> $this->resolve($node->right); |
|
348 | } |
||
349 | |||
350 | 2 | protected function resolveExprBinaryOpConcat(Expr\BinaryOp\Concat $node) |
|
351 | { |
||
352 | 2 | return $this->resolve($node->left) . $this->resolve($node->right); |
|
353 | } |
||
354 | |||
355 | 1 | protected function resolveExprTernary(Expr\Ternary $node) |
|
356 | { |
||
357 | 1 | if (isset($node->if)) { |
|
358 | // Full syntax $a ? $b : $c; |
||
359 | |||
360 | return $this->resolve($node->cond) ? $this->resolve($node->if) : $this->resolve($node->else); |
||
361 | } else { |
||
362 | // Short syntax $a ?: $c; |
||
363 | |||
364 | 1 | return $this->resolve($node->cond) ?: $this->resolve($node->else); |
|
365 | } |
||
366 | } |
||
367 | |||
368 | 1 | protected function resolveExprBinaryOpSmallerOrEqual(Expr\BinaryOp\SmallerOrEqual $node) |
|
369 | { |
||
370 | 1 | return $this->resolve($node->left) <= $this->resolve($node->right); |
|
371 | } |
||
372 | |||
373 | 1 | protected function resolveExprBinaryOpGreaterOrEqual(Expr\BinaryOp\GreaterOrEqual $node) |
|
374 | { |
||
375 | 1 | return $this->resolve($node->left) >= $this->resolve($node->right); |
|
376 | } |
||
377 | |||
378 | 1 | protected function resolveExprBinaryOpEqual(Expr\BinaryOp\Equal $node) |
|
379 | { |
||
380 | 1 | return $this->resolve($node->left) == $this->resolve($node->right); |
|
381 | } |
||
382 | |||
383 | 1 | protected function resolveExprBinaryOpNotEqual(Expr\BinaryOp\NotEqual $node) |
|
384 | { |
||
385 | 1 | return $this->resolve($node->left) != $this->resolve($node->right); |
|
386 | } |
||
387 | |||
388 | 1 | protected function resolveExprBinaryOpSmaller(Expr\BinaryOp\Smaller $node) |
|
389 | { |
||
390 | 1 | return $this->resolve($node->left) < $this->resolve($node->right); |
|
391 | } |
||
392 | |||
393 | 2 | protected function resolveExprBinaryOpGreater(Expr\BinaryOp\Greater $node) |
|
394 | { |
||
395 | 2 | return $this->resolve($node->left) > $this->resolve($node->right); |
|
396 | } |
||
397 | |||
398 | 1 | protected function resolveExprBinaryOpIdentical(Expr\BinaryOp\Identical $node) |
|
399 | { |
||
400 | 1 | return $this->resolve($node->left) === $this->resolve($node->right); |
|
401 | } |
||
402 | |||
403 | 1 | protected function resolveExprBinaryOpNotIdentical(Expr\BinaryOp\NotIdentical $node) |
|
404 | { |
||
405 | 1 | return $this->resolve($node->left) !== $this->resolve($node->right); |
|
406 | } |
||
407 | |||
408 | 1 | protected function resolveExprBinaryOpBooleanAnd(Expr\BinaryOp\BooleanAnd $node) |
|
409 | { |
||
410 | 1 | return $this->resolve($node->left) && $this->resolve($node->right); |
|
411 | } |
||
412 | |||
413 | 1 | protected function resolveExprBinaryOpLogicalAnd(Expr\BinaryOp\LogicalAnd $node) |
|
414 | { |
||
415 | 1 | return $this->resolve($node->left) and $this->resolve($node->right); |
|
416 | } |
||
417 | |||
418 | 1 | protected function resolveExprBinaryOpBooleanOr(Expr\BinaryOp\BooleanOr $node) |
|
419 | { |
||
420 | 1 | return $this->resolve($node->left) || $this->resolve($node->right); |
|
421 | } |
||
422 | |||
423 | 1 | protected function resolveExprBinaryOpLogicalOr(Expr\BinaryOp\LogicalOr $node) |
|
424 | { |
||
425 | 1 | return $this->resolve($node->left) or $this->resolve($node->right); |
|
426 | } |
||
427 | |||
428 | 1 | protected function resolveExprBinaryOpLogicalXor(Expr\BinaryOp\LogicalXor $node) |
|
429 | { |
||
430 | 1 | return $this->resolve($node->left) xor $this->resolve($node->right); |
|
431 | } |
||
432 | |||
433 | /** |
||
434 | * Utility method to fetch reflection class instance by name |
||
435 | * |
||
436 | * Supports: |
||
437 | * 'self' keyword |
||
438 | * 'parent' keyword |
||
439 | * not-FQN class names |
||
440 | * |
||
441 | * @param Node\Name $node Class name node |
||
442 | * |
||
443 | * @return bool|\ReflectionClass |
||
444 | * |
||
445 | * @throws ReflectionException |
||
446 | */ |
||
447 | 11 | private function fetchReflectionClass(Node\Name $node) |
|
448 | { |
||
449 | 11 | $className = $node->toString(); |
|
450 | 11 | $isFQNClass = $node instanceof Node\Name\FullyQualified; |
|
451 | 11 | if ($isFQNClass) { |
|
452 | // check to see if the class is already loaded and is safe to use |
||
453 | // PHP's ReflectionClass to determine if the class is user defined |
||
454 | 5 | if (class_exists($className, false)) { |
|
455 | 5 | $refClass = new \ReflectionClass($className); |
|
456 | 5 | if (!$refClass->isUserDefined()) { |
|
457 | 2 | return $refClass; |
|
458 | } |
||
459 | } |
||
460 | 4 | return new ReflectionClass($className); |
|
461 | } |
||
462 | |||
463 | 8 | if ('self' === $className) { |
|
464 | 8 | if ($this->context instanceof \ReflectionClass) { |
|
465 | 8 | return $this->context; |
|
466 | } elseif (method_exists($this->context, 'getDeclaringClass')) { |
||
467 | return $this->context->getDeclaringClass(); |
||
468 | } |
||
469 | } |
||
470 | |||
471 | 1 | if ('parent' === $className) { |
|
472 | 1 | if ($this->context instanceof \ReflectionClass) { |
|
473 | 1 | return $this->context->getParentClass(); |
|
474 | } elseif (method_exists($this->context, 'getDeclaringClass')) { |
||
475 | return $this->context->getDeclaringClass()->getParentClass(); |
||
476 | } |
||
477 | } |
||
478 | |||
479 | if (method_exists($this->context, 'getFileName')) { |
||
480 | /** @var ReflectionFileNamespace|null $fileNamespace */ |
||
481 | $fileName = $this->context->getFileName(); |
||
482 | $namespaceName = $this->resolveScalarMagicConstNamespace(); |
||
483 | |||
484 | $fileNamespace = new ReflectionFileNamespace($fileName, $namespaceName); |
||
485 | return $fileNamespace->getClass($className); |
||
486 | } |
||
487 | |||
488 | throw new ReflectionException("Can not resolve class $className"); |
||
489 | } |
||
490 | } |
||
491 |