Completed
Pull Request — master (#82)
by Loren
03:39
created

NodeExpressionResolver::resolveExprConstFetch()   D

Complexity

Conditions 9
Paths 20

Size

Total Lines 34
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 9

Importance

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