Completed
Pull Request — master (#114)
by
unknown
46:47 queued 21:47
created

NodeExpressionResolver::resolveExprUnaryMinus()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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