Completed
Push — master ( 49e6cb...c60371 )
by Alexander
02:20
created

resolveScalarMagicConstClass()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4.8437

Importance

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