Completed
Pull Request — master (#107)
by Alexander
03:29
created

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