Completed
Push — master ( c4d2d4...eae51e )
by Alexander
9s
created

src/ValueResolver/NodeExpressionResolver.php (6 issues)

Upgrade to new PHP Analysis Engine

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();
0 ignored issues
show
Consider using $this->context->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
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
Consider using $this->context->name. There is an issue with getName() and APC-enabled PHP versions.
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
Consider using $this->context->name. There is an issue with getName() and APC-enabled PHP versions.
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;
0 ignored issues
show
$fileNamespace is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
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