Completed
Push — master ( 555240...fa2155 )
by Alexander
115:41 queued 90:43
created

NodeExpressionResolver::fetchReflectionClass()   C

Complexity

Conditions 11
Paths 17

Size

Total Lines 51

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 19.7549

Importance

Changes 0
Metric Value
dl 0
loc 51
ccs 14
cts 24
cp 0.5833
rs 6.9224
c 0
b 0
f 0
cc 11
nc 17
nop 1
crap 19.7549

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 resolveScalarString(String_ $node): string
146
    {
147
        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 1
        return '';
159
    }
160 1
161 1
    protected function resolveScalarMagicConstFunction(): string
162
    {
163
        if ($this->context instanceof ReflectionFunctionAbstract) {
164
            return $this->context->getName();
165
        }
166
167 30
        return '';
168
    }
169 30
170 27
    protected function resolveScalarMagicConstNamespace(): string
171
    {
172
        if (method_exists($this->context, 'getNamespaceName')) {
173 3
            return $this->context->getNamespaceName();
174 3
        }
175
176
        if ($this->context instanceof ReflectionFileNamespace) {
177
            return $this->context->getName();
178
        }
179
180 11
        return '';
181
    }
182 11
183 5
    protected function resolveScalarMagicConstClass(): string
184
    {
185 6
        if ($this->context instanceof \ReflectionClass) {
186
            return $this->context->name;
187
        }
188
        if (method_exists($this->context, 'getDeclaringClass')) {
189
            $declaringClass = $this->context->getDeclaringClass();
190
            if ($declaringClass instanceof \ReflectionClass) {
191
                return $declaringClass->name;
192 6
            }
193
        }
194
195 4
        return '';
196
    }
197 4
198 4
    protected function resolveScalarMagicConstDir(): string
199
    {
200
        if (method_exists($this->context, 'getFileName')) {
201
            return dirname($this->context->getFileName());
202
        }
203
204 7
        return '';
205
    }
206 7
207 7
    protected function resolveScalarMagicConstFile(): string
208
    {
209
        if (method_exists($this->context, 'getFileName')) {
210
            return $this->context->getFileName();
211
        }
212
213 7
        return '';
214
    }
215 7
216
    protected function resolveScalarMagicConstLine(Line $node): int
217
    {
218 2
        return $node->hasAttribute('startLine') ? $node->getAttribute('startLine') : 0;
219
    }
220 2
221 2
    protected function resolveScalarMagicConstTrait(): string
222
    {
223
        if ($this->context instanceof \ReflectionClass && $this->context->isTrait()) {
224
            return $this->context->name;
225
        }
226
227 28
        return '';
228
    }
229 28
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 24
        $constantName  = $node->name->toString();
237 24
238 24
        if (!$isFQNConstant && method_exists($this->context, 'getFileName')) {
239 24
            $fileName      = $this->context->getFileName();
240 24
            $namespaceName = $this->resolveScalarMagicConstNamespace();
241 13
            $fileNamespace = new ReflectionFileNamespace($fileName, $namespaceName);
242 13
            if ($fileNamespace->hasConstant($constantName)) {
243 13
                $constantValue = $fileNamespace->getConstant($constantName);
244
                $constantName  = $fileNamespace->getName() . '\\' . $constantName;
245
                $isResolved    = true;
246
            }
247
        }
248 28
249 26
        if (!$isResolved && defined($constantName)) {
250
            $constantValue = constant($constantName);
251
        }
252 28
253 16
        if ($this->nodeLevel === 1 && !isset(self::$notConstants[$constantName])) {
254 16
            $this->isConstant   = true;
255
            $this->constantName = $constantName;
256
        }
257 28
258
        return $constantValue;
259
    }
260 15
261
    protected function resolveExprClassConstFetch(Expr\ClassConstFetch $node)
262 15
    {
263 15
        $classToReflect = $node->class;
264 3
        if (!($classToReflect instanceof Node\Name)) {
265 3
            $classToReflect = $this->resolve($classToReflect) ?: $classToReflect;
266 2
            if (!is_string($classToReflect)) {
267 2
                $reason = 'Unable';
268 1
                if ($classToReflect instanceof Expr) {
269 1
                    $methodName = $this->getDispatchMethodFor($classToReflect);
270
                    $reason     = "Method " . __CLASS__ . "::{$methodName}() not found trying";
271 2
                }
272
                throw new ReflectionException("$reason to resolve class constant.");
273
            }
274
            // Strings evaluated as class names are always treated as fully
275 1
            // qualified.
276
            $classToReflect = new Node\Name\FullyQualified(ltrim($classToReflect, '\\'));
277 13
        }
278 13
        $refClass     = $this->fetchReflectionClass($classToReflect);
279
        $constantName = ($node->name instanceof Expr\Error) ? '' : $node->name->toString();
280
281 13
        // special handling of ::class constants
282 3
        if ('class' === $constantName) {
283
            return $refClass->getName();
284
        }
285 11
286 11
        $this->isConstant   = true;
287
        $this->constantName = $classToReflect . '::' . $constantName;
288 11
289
        return $refClass->getConstant($constantName);
290
    }
291 9
292
    protected function resolveExprArray(Expr\Array_ $node): array
293 9
    {
294 9
        $result = [];
295 8
        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
            $result[$itemKey] = $itemValue;
299
        }
300 9
301
        return $result;
302
    }
303 3
304
    protected function resolveExprBinaryOpPlus(Expr\BinaryOp\Plus $node)
305 3
    {
306
        return $this->resolve($node->left) + $this->resolve($node->right);
307
    }
308 1
309
    protected function resolveExprBinaryOpMinus(Expr\BinaryOp\Minus $node)
310 1
    {
311
        return $this->resolve($node->left) - $this->resolve($node->right);
312
    }
313 2
314
    protected function resolveExprBinaryOpMul(Expr\BinaryOp\Mul $node)
315 2
    {
316
        return $this->resolve($node->left) * $this->resolve($node->right);
317
    }
318 1
319
    protected function resolveExprBinaryOpPow(Expr\BinaryOp\Pow $node)
320 1
    {
321
        return $this->resolve($node->left) ** $this->resolve($node->right);
322
    }
323 1
324
    protected function resolveExprBinaryOpDiv(Expr\BinaryOp\Div $node)
325 1
    {
326
        return $this->resolve($node->left) / $this->resolve($node->right);
327
    }
328 1
329
    protected function resolveExprBinaryOpMod(Expr\BinaryOp\Mod $node): int
330 1
    {
331
        return $this->resolve($node->left) % $this->resolve($node->right);
332
    }
333 1
334
    protected function resolveExprBooleanNot(Expr\BooleanNot $node): bool
335 1
    {
336
        return !$this->resolve($node->expr);
337
    }
338 1
339
    protected function resolveExprBitwiseNot(Expr\BitwiseNot $node)
340 1
    {
341
        return ~$this->resolve($node->expr);
342
    }
343 1
344
    protected function resolveExprBinaryOpBitwiseOr(Expr\BinaryOp\BitwiseOr $node): int
345 1
    {
346
        return $this->resolve($node->left) | $this->resolve($node->right);
347
    }
348 1
349
    protected function resolveExprBinaryOpBitwiseAnd(Expr\BinaryOp\BitwiseAnd $node): int
350 1
    {
351
        return $this->resolve($node->left) & $this->resolve($node->right);
352
    }
353 1
354
    protected function resolveExprBinaryOpBitwiseXor(Expr\BinaryOp\BitwiseXor $node): int
355 1
    {
356
        return $this->resolve($node->left) ^ $this->resolve($node->right);
357
    }
358 1
359
    protected function resolveExprBinaryOpShiftLeft(Expr\BinaryOp\ShiftLeft $node): int
360 1
    {
361
        return $this->resolve($node->left) << $this->resolve($node->right);
362
    }
363 1
364
    protected function resolveExprBinaryOpShiftRight(Expr\BinaryOp\ShiftRight $node): int
365 1
    {
366
        return $this->resolve($node->left) >> $this->resolve($node->right);
367
    }
368 3
369
    protected function resolveExprBinaryOpConcat(Expr\BinaryOp\Concat $node): string
370 3
    {
371
        return $this->resolve($node->left) . $this->resolve($node->right);
372
    }
373 1
374
    protected function resolveExprTernary(Expr\Ternary $node)
375 1
    {
376
        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
    protected function resolveExprBinaryOpSmallerOrEqual(Expr\BinaryOp\SmallerOrEqual $node): bool
386 1
    {
387
        return $this->resolve($node->left) <= $this->resolve($node->right);
388 1
    }
389
390
    protected function resolveExprBinaryOpGreaterOrEqual(Expr\BinaryOp\GreaterOrEqual $node): bool
391 1
    {
392
        return $this->resolve($node->left) >= $this->resolve($node->right);
393 1
    }
394
395
    protected function resolveExprBinaryOpEqual(Expr\BinaryOp\Equal $node): bool
396 1
    {
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 1
        /** @noinspection TypeUnsafeComparisonInspection */
404
        return $this->resolve($node->left) != $this->resolve($node->right);
405
    }
406 1
407
    protected function resolveExprBinaryOpSmaller(Expr\BinaryOp\Smaller $node): bool
408 1
    {
409
        return $this->resolve($node->left) < $this->resolve($node->right);
410
    }
411 2
412
    protected function resolveExprBinaryOpGreater(Expr\BinaryOp\Greater $node): bool
413 2
    {
414
        return $this->resolve($node->left) > $this->resolve($node->right);
415
    }
416 1
417
    protected function resolveExprBinaryOpIdentical(Expr\BinaryOp\Identical $node): bool
418 1
    {
419
        return $this->resolve($node->left) === $this->resolve($node->right);
420
    }
421 1
422
    protected function resolveExprBinaryOpNotIdentical(Expr\BinaryOp\NotIdentical $node): bool
423 1
    {
424
        return $this->resolve($node->left) !== $this->resolve($node->right);
425
    }
426 1
427
    protected function resolveExprBinaryOpBooleanAnd(Expr\BinaryOp\BooleanAnd $node): bool
428 1
    {
429
        return $this->resolve($node->left) && $this->resolve($node->right);
430
    }
431 1
432
    protected function resolveExprBinaryOpLogicalAnd(Expr\BinaryOp\LogicalAnd $node): bool
433 1
    {
434
        return $this->resolve($node->left) and $this->resolve($node->right);
435
    }
436 1
437
    protected function resolveExprBinaryOpBooleanOr(Expr\BinaryOp\BooleanOr $node): bool
438 1
    {
439
        return $this->resolve($node->left) || $this->resolve($node->right);
440
    }
441 1
442
    protected function resolveExprBinaryOpLogicalOr(Expr\BinaryOp\LogicalOr $node): bool
443 1
    {
444
        return $this->resolve($node->left) or $this->resolve($node->right);
445
    }
446 1
447
    protected function resolveExprBinaryOpLogicalXor(Expr\BinaryOp\LogicalXor $node): bool
448 1
    {
449
        return $this->resolve($node->left) xor $this->resolve($node->right);
450
    }
451 53
452
    private function getDispatchMethodFor(Node $node): string
453 53
    {
454 53
        $nodeType = $node->getType();
455
456
        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 13
     * @throws ReflectionException
472
     */
473 13
    private function fetchReflectionClass(Node\Name $node)
474 13
    {
475 13
        $className  = $node->toString();
476
        $isFQNClass = $node instanceof Node\Name\FullyQualified;
477
        if ($isFQNClass) {
478 6
            // check to see if the class is already loaded and is safe to use
479 6
            // PHP's ReflectionClass to determine if the class is user defined
480 6
            if (class_exists($className, false)) {
481 3
                $refClass = new \ReflectionClass($className);
482
                if (!$refClass->isUserDefined()) {
483
                    return $refClass;
484 4
                }
485
            }
486
487 9
            return new ReflectionClass($className);
488 9
        }
489 9
490
        if ('self' === $className) {
491
            if ($this->context instanceof \ReflectionClass) {
492
                return $this->context;
493
            }
494
495 1
            if (method_exists($this->context, 'getDeclaringClass')) {
496 1
                return $this->context->getDeclaringClass();
497 1
            }
498
        }
499
500
        if ('parent' === $className) {
501
            if ($this->context instanceof \ReflectionClass) {
502
                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