Completed
Push — master ( 5f68cd...6c5557 )
by Alexander
02:53
created

NodeExpressionResolver   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 304
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 75.57%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 51
c 2
b 1
f 0
lcom 1
cbo 13
dl 0
loc 304
ccs 99
cts 131
cp 0.7557
rs 8.3206

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getConstantName() 0 4 1
A getValue() 0 4 1
A isConstant() 0 4 1
A process() 0 7 1
D fetchReflectionClass() 0 35 9
A resolve() 0 17 2
A resolveScalarDNumber() 0 4 1
A resolveScalarLNumber() 0 4 1
A resolveScalarString() 0 4 1
A resolveScalarMagicConstLine() 0 4 2
C resolveExprConstFetch() 0 34 8
A resolveExprClassConstFetch() 0 12 2
A resolveExprArray() 0 11 3
A resolveScalarMagicConstFunction() 0 8 2
A resolveScalarMagicConstNamespace() 0 8 2
A resolveScalarMagicConstDir() 0 8 2
A resolveScalarMagicConstFile() 0 8 2
A resolveScalarMagicConstTrait() 0 8 3
A resolveScalarMagicConstMethod() 0 10 2
A resolveScalarMagicConstClass() 0 14 4

How to fix   Complexity   

Complex Class

Complex classes like NodeExpressionResolver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use NodeExpressionResolver, and based on these observations, apply Extract Interface, too.

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
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 13
    public function __construct($context)
72
    {
73 13
        $this->context = $context;
74 13
    }
75
76 8
    public function getConstantName()
77
    {
78 8
        return $this->constantName;
79
    }
80
81 13
    public function getValue()
82
    {
83 13
        return $this->value;
84
    }
85
86 8
    public function isConstant()
87
    {
88 8
        return $this->isConstant;
89
    }
90
91
    /**
92
     * {@inheritDoc}
93
     */
94 13
    public function process(Node $node)
95
    {
96 13
        $this->nodeLevel    = 0;
97 13
        $this->isConstant   = false;
98 13
        $this->constantName = null;
99 13
        $this->value        = $this->resolve($node);
100 13
    }
101
102
    /**
103
     * Resolves node into valid value
104
     *
105
     * @param Node $node
106
     *
107
     * @return mixed
108
     */
109 13
    protected function resolve(Node $node)
110
    {
111 13
        $value = null;
112
        try {
113 13
            ++$this->nodeLevel;
114
115 13
            $nodeType   = $node->getType();
116 13
            $methodName = 'resolve' . str_replace('_', '', $nodeType);
117 13
            if (method_exists($this, $methodName)) {
118 13
                $value = $this->$methodName($node);
119 13
            }
120 13
        } finally {
121 13
            --$this->nodeLevel;
122
        }
123
124 13
        return $value;
125
    }
126
127 2
    protected function resolveScalarDNumber(Scalar\DNumber $node)
128
    {
129 2
        return $node->value;
130
    }
131
132 11
    protected function resolveScalarLNumber(Scalar\LNumber $node)
133
    {
134 11
        return $node->value;
135
    }
136
137 5
    protected function resolveScalarString(Scalar\String_ $node)
138
    {
139 5
        return $node->value;
140
    }
141
142
    protected function resolveScalarMagicConstMethod()
143
    {
144
        if ($this->context instanceof \ReflectionMethod) {
145
            $fullName = $this->context->getDeclaringClass()->getName() . '::' . $this->context->getShortName();
0 ignored issues
show
introduced by
Consider using $this->context->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
146
147
            return $fullName;
148
        }
149
150
        return '';
151
    }
152
153
    protected function resolveScalarMagicConstFunction()
154
    {
155
        if ($this->context instanceof \ReflectionFunctionAbstract) {
156
            return $this->context->getName();
157
        }
158
159
        return '';
160
    }
161
162 7
    protected function resolveScalarMagicConstNamespace()
163
    {
164 7
        if (method_exists($this->context, 'getNamespaceName')) {
165 7
            return $this->context->getNamespaceName();
166
        }
167
168
        return '';
169
    }
170
171 7
    protected function resolveScalarMagicConstClass()
172
    {
173 7
        if ($this->context instanceof \ReflectionClass) {
174 1
            return $this->context->getName();
0 ignored issues
show
Bug introduced by
Consider using $this->context->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
175
        }
176 6
        if (method_exists($this->context, 'getDeclaringClass')) {
177
            $declaringClass = $this->context->getDeclaringClass();
178
            if ($declaringClass instanceof \ReflectionClass) {
179
                return $declaringClass->getName();
0 ignored issues
show
Bug introduced by
Consider using $declaringClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
180
            }
181
        }
182
183 6
        return '';
184
    }
185
186 1
    protected function resolveScalarMagicConstDir()
187
    {
188 1
        if (method_exists($this->context, 'getFileName')) {
189 1
            return dirname($this->context->getFileName());
190
        }
191
192
        return '';
193
    }
194
195 1
    protected function resolveScalarMagicConstFile()
196
    {
197 1
        if (method_exists($this->context, 'getFileName')) {
198 1
            return $this->context->getFileName();
199
        }
200
201
        return '';
202
    }
203
204 1
    protected function resolveScalarMagicConstLine(MagicConst\Line $node)
205
    {
206 1
        return $node->hasAttribute('startLine') ? $node->getAttribute('startLine') : 0;
207
    }
208
209
    protected function resolveScalarMagicConstTrait()
210
    {
211
        if ($this->context instanceof \ReflectionClass && $this->context->isTrait()) {
212
            return $this->context->getName();
0 ignored issues
show
Bug introduced by
Consider using $this->context->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
213
        }
214
215
        return '';
216
    }
217
218 11
    protected function resolveExprConstFetch(Expr\ConstFetch $node)
219
    {
220 11
        $constantValue = null;
221 11
        $isResolved    = false;
222
223
        /** @var ReflectionFileNamespace|null $fileNamespace */
224 11
        $fileNamespace = null;
0 ignored issues
show
Unused Code introduced by
$fileNamespace is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
225 11
        $isFQNConstant = $node->name instanceof Node\Name\FullyQualified;
226 11
        $constantName  = $node->name->toString();
227
228 11
        if (!$isFQNConstant) {
229 11
            if (method_exists($this->context, 'getFileName')) {
230 11
                $fileName      = $this->context->getFileName();
231 11
                $namespaceName = $this->context->getNamespaceName();
232 11
                $fileNamespace = new ReflectionFileNamespace($fileName, $namespaceName);
233 11
                if ($fileNamespace->hasConstant($constantName)) {
234 8
                    $constantValue = $fileNamespace->getConstant($constantName);
235 8
                    $constantName  = $fileNamespace->getName() . '\\' . $constantName;
236 8
                    $isResolved    = true;
237 8
                }
238 11
            }
239 11
        }
240
241 11
        if (!$isResolved && defined($constantName)) {
242 10
            $constantValue = constant($constantName);
243 10
        }
244
245 11
        if ($this->nodeLevel === 1 && !isset(self::$notConstants[$constantName])) {
246 8
            $this->isConstant   = true;
247 8
            $this->constantName = $constantName;
248 8
        }
249
250 11
        return $constantValue;
251
    }
252
253 3
    protected function resolveExprClassConstFetch(Expr\ClassConstFetch $node)
254
    {
255 3
        $refClass     = $this->fetchReflectionClass($node->class);
256 3
        $constantName = $node->name;
257
258
        // special handling of ::class constants
259 3
        if ('class' === $constantName) {
260 1
            return $refClass->getName();
261
        }
262
263 3
        return $refClass->getConstant($constantName);
264
    }
265
266 7
    protected function resolveExprArray(Expr\Array_ $node)
267
    {
268 7
        $result = [];
269 7
        foreach ($node->items as $itemIndex => $arrayItem) {
270 7
            $itemValue = $this->resolve($arrayItem->value);
271 7
            $itemKey   = isset($arrayItem->key) ? $this->resolve($arrayItem->key) : $itemIndex;
272 7
            $result[$itemKey] = $itemValue;
273 7
        }
274
275 7
        return $result;
276
    }
277
278
    /**
279
     * Utility method to fetch reflection class instance by name
280
     *
281
     * Supports:
282
     *   'self' keyword
283
     *   'parent' keyword
284
     *    not-FQN class names
285
     *
286
     * @param Node\Name $node Class name node
287
     *
288
     * @return bool|\ReflectionClass
289
     *
290
     * @throws ReflectionException
291
     */
292 3
    private function fetchReflectionClass(Node\Name $node)
293
    {
294 3
        $className  = $node->toString();
295 3
        $isFQNClass = $node instanceof Node\Name\FullyQualified;
296 3
        if ($isFQNClass) {
297 1
            return new ReflectionClass($className);
298
        }
299
300 3
        if ('self' === $className) {
301 3
            if ($this->context instanceof \ReflectionClass) {
302 3
                return $this->context;
303
            } elseif (method_exists($this->context, 'getDeclaringClass')) {
304
                return $this->context->getDeclaringClass();
305
            }
306
        }
307
308 1
        if ('parent' === $className) {
309 1
            if ($this->context instanceof \ReflectionClass) {
310 1
                return $this->context->getParentClass();
311
            } elseif (method_exists($this->context, 'getDeclaringClass')) {
312
                return $this->context->getDeclaringClass()->getParentClass();
313
            }
314
        }
315
316
        if (method_exists($this->context, 'getFileName')) {
317
            /** @var ReflectionFileNamespace|null $fileNamespace */
318
            $fileName      = $this->context->getFileName();
319
            $namespaceName = $this->context->getNamespaceName();
320
321
            $fileNamespace = new ReflectionFileNamespace($fileName, $namespaceName);
322
            return $fileNamespace->getClass($className);
323
        }
324
325
        throw new ReflectionException("Can not resolve class $className");
326
    }
327
}
328