Completed
Pull Request — master (#114)
by
unknown
22:23
created

resolveExprBinaryOpSmaller()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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