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

ReflectionFileNamespace   D

Complexity

Total Complexity 55

Size/Duplication

Total Lines 436
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Test Coverage

Coverage 96.03%

Importance

Changes 0
Metric Value
wmc 55
lcom 1
cbo 16
dl 0
loc 436
ccs 121
cts 126
cp 0.9603
rs 4.5205
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 3
A getClass() 0 8 2
A getClasses() 0 8 2
A getConstant() 0 8 2
A getConstants() 0 16 4
A getDocComment() 0 11 2
A getEndLine() 0 4 1
A getFileName() 0 4 1
A getFunction() 0 8 2
A getFunctions() 0 8 2
A getName() 0 6 2
A getNamespaceAliases() 0 8 2
A getLastTokenPosition() 0 10 1
A getStartLine() 0 4 1
A hasClass() 0 6 1
A hasConstant() 0 6 1
A hasFunction() 0 6 1
A findClasses() 0 17 4
A findFunctions() 0 18 4
C findConstants() 0 40 11
B findNamespaceAliases() 0 18 5
A getNode() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like ReflectionFileNamespace 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 ReflectionFileNamespace, 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;
12
13
use Go\ParserReflection\Instrument\PathResolver;
14
use Go\ParserReflection\ValueResolver\NodeExpressionResolver;
15
use PhpParser\Node;
16
use PhpParser\Node\Expr\FuncCall;
17
use PhpParser\Node\Name;
18
use PhpParser\Node\Stmt\ClassLike;
19
use PhpParser\Node\Stmt\Const_;
20
use PhpParser\Node\Stmt\Function_;
21
use PhpParser\Node\Stmt\Namespace_;
22
use PhpParser\Node\Stmt\Use_;
23
24
/**
25
 * AST-based reflection for the concrete namespace in the file
26
 */
27
class ReflectionFileNamespace
28
{
29
    /**
30
     * List of classes in the namespace
31
     *
32
     * @var array|ReflectionClass[]
33
     */
34
    protected $fileClasses;
35
36
    /**
37
     * List of functions in the namespace
38
     *
39
     * @var array|ReflectionFunction[]
40
     */
41
    protected $fileFunctions;
42
43
    /**
44
     * List of constants in the namespace
45
     *
46
     * @var array
47
     */
48
    protected $fileConstants;
49
50
    /**
51
     * List of constants in the namespace including defined via "define(...)"
52
     *
53
     * @var array
54
     */
55
    protected $fileConstantsWithDefined;
56
57
    /**
58
     * List of imported namespaces (aliases)
59
     *
60
     * @var array
61
     */
62
    protected $fileNamespaceAliases;
63
64
    /**
65
     * Namespace node
66
     *
67
     * @var Namespace_
68
     */
69
    private $namespaceNode;
70
71
    /**
72
     * Name of the file
73
     *
74
     * @var string
75
     */
76
    private $fileName;
77
78
    /**
79
     * File namespace constructor
80
     *
81
     * @param string          $fileName      Name of the file
82
     * @param string          $namespaceName Name of the namespace
83
     * @param Namespace_|null $namespaceNode Optional AST-node for this namespace block
84
     */
85 8977
    public function __construct($fileName, $namespaceName, Namespace_ $namespaceNode = null)
86
    {
87 8977
        if (!is_string($fileName)) {
88 2
            throw new \InvalidArgumentException(
89 2
                sprintf(
90 2
                    '$fileName must be a string, but a %s was passed',
91 2
                    gettype($fileName)
92
                )
93
            );
94
        }
95 8977
        $fileName = PathResolver::realpath($fileName);
96 8977
        if (!$namespaceNode) {
97 37
            $namespaceNode = ReflectionEngine::parseFileNamespace($fileName, $namespaceName);
98
        }
99 8977
        $this->namespaceNode = $namespaceNode;
100 8977
        $this->fileName      = $fileName;
0 ignored issues
show
Documentation Bug introduced by Lisachenko Alexander
It seems like $fileName can also be of type array or boolean. However, the property $fileName is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
101 8977
    }
102
103
    /**
104
     * Returns the concrete class from the file namespace or false if there is no class
105
     *
106
     * @param string $className
107
     *
108
     * @return bool|ReflectionClass
109
     */
110 8400
    public function getClass($className)
111
    {
112 8400
        if ($this->hasClass($className)) {
113 8400
            return $this->fileClasses[$className];
114
        }
115
116 112
        return false;
117
    }
118
119
    /**
120
     * Gets list of classes in the namespace
121
     *
122
     * @return ReflectionClass[]|array
123
     */
124 8403
    public function getClasses()
125
    {
126 8403
        if (!isset($this->fileClasses)) {
127 8403
            $this->fileClasses = $this->findClasses();
128
        }
129
130 8403
        return $this->fileClasses;
131
    }
132
133
    /**
134
     * Returns a value for the constant
135
     *
136
     * @param string $constantName name of the constant to fetch
137
     *
138
     * @return bool|mixed
139
     */
140 19
    public function getConstant($constantName)
141
    {
142 19
        if ($this->hasConstant($constantName)) {
143 19
            return $this->fileConstants[$constantName];
144
        }
145
146 2
        return false;
147
    }
148
149
    /**
150
     * Returns a list of defined constants in the namespace
151
     *
152
     * @param bool $withDefined Include constants defined via "define(...)" in results.
153
     *
154
     * @return array
155
     */
156 40
    public function getConstants($withDefined = false)
157
    {
158 40
        if ($withDefined) {
159 2
            if (!isset($this->fileConstantsWithDefined)) {
160 2
                $this->fileConstantsWithDefined = $this->findConstants(true);
161
            }
162
163 2
            return $this->fileConstantsWithDefined;
164
        }
165
166 40
        if (!isset($this->fileConstants)) {
167 40
            $this->fileConstants = $this->findConstants();
168
        }
169
170 40
        return $this->fileConstants;
171
    }
172
173
    /**
174
     * Gets doc comments from a class.
175
     *
176
     * @return string|false The doc comment if it exists, otherwise "false"
177
     */
178 1
    public function getDocComment()
179
    {
180 1
        $docComment = false;
181 1
        $comments   = $this->namespaceNode->getAttribute('comments');
182
183 1
        if ($comments) {
184 1
            $docComment = (string)$comments[0];
185
        }
186
187 1
        return $docComment;
188
    }
189
190
    /**
191
     * Gets starting line number
192
     *
193
     * @return integer
194
     */
195 1
    public function getEndLine()
196
    {
197 1
        return $this->namespaceNode->getAttribute('endLine');
198
    }
199
200
    /**
201
     * Returns the name of file
202
     *
203
     * @return string
204
     */
205 5
    public function getFileName()
206
    {
207 5
        return $this->fileName;
208
    }
209
210
    /**
211
     * Returns the concrete function from the file namespace or false if there is no function
212
     *
213
     * @param string $functionName
214
     *
215
     * @return bool|ReflectionFunction
216
     */
217 554
    public function getFunction($functionName)
218
    {
219 554
        if ($this->hasFunction($functionName)) {
220 554
            return $this->fileFunctions[$functionName];
221
        }
222
223 1
        return false;
224
    }
225
226
    /**
227
     * Gets list of functions in the namespace
228
     *
229
     * @return ReflectionFunction[]|array
230
     */
231 558
    public function getFunctions()
232
    {
233 558
        if (!isset($this->fileFunctions)) {
234 558
            $this->fileFunctions = $this->findFunctions();
235
        }
236
237 558
        return $this->fileFunctions;
238
    }
239
240
    /**
241
     * Gets namespace name
242
     *
243
     * @return string
244
     */
245 8966
    public function getName()
246
    {
247 8966
        $nameNode = $this->namespaceNode->name;
248
249 8966
        return $nameNode ? $nameNode->toString() : '';
250
    }
251
252
    /**
253
     * Returns a list of namespace aliases
254
     *
255
     * @return array
256
     */
257 1
    public function getNamespaceAliases()
258
    {
259 1
        if (!isset($this->fileNamespaceAliases)) {
260 1
            $this->fileNamespaceAliases = $this->findNamespaceAliases();
261
        }
262
263 1
        return $this->fileNamespaceAliases;
264
    }
265
266
    /**
267
     * Returns an AST-node for namespace
268
     *
269
     * @return Namespace_
270
     */
271 8394
    public function getNode()
272
    {
273 8394
        return $this->namespaceNode;
274
    }
275
276
    /**
277
     * Helper method to access last token position for namespace
278
     *
279
     * This method is useful because namespace can be declared with braces or without them
280
     */
281
    public function getLastTokenPosition()
282
    {
283
        $endNamespaceTokenPosition = $this->namespaceNode->getAttribute('endTokenPos');
284
285
        /** @var Node $lastNamespaceNode */
286
        $lastNamespaceNode         = end($this->namespaceNode->stmts);
287
        $endStatementTokenPosition = $lastNamespaceNode->getAttribute('endTokenPos');
288
289
        return max($endNamespaceTokenPosition, $endStatementTokenPosition);
290
    }
291
292
    /**
293
     * Gets starting line number
294
     *
295
     * @return integer
296
     */
297 1
    public function getStartLine()
298
    {
299 1
        return $this->namespaceNode->getAttribute('startLine');
300
    }
301
302
    /**
303
     * Checks if the given class is present in this filenamespace
304
     *
305
     * @param string $className
306
     *
307
     * @return bool
308
     */
309 8401
    public function hasClass($className)
310
    {
311 8401
        $classes = $this->getClasses();
312
313 8401
        return isset($classes[$className]);
314
    }
315
316
    /**
317
     * Checks if the given constant is present in this filenamespace
318
     *
319
     * @param string $constantName
320
     *
321
     * @return bool
322
     */
323 40
    public function hasConstant($constantName)
324
    {
325 40
        $constants = $this->getConstants();
326
327 40
        return isset($constants[$constantName]);
328
    }
329
330
    /**
331
     * Checks if the given function is present in this filenamespace
332
     *
333
     * @param string $functionName
334
     *
335
     * @return bool
336
     */
337 555
    public function hasFunction($functionName)
338
    {
339 555
        $functions = $this->getFunctions();
340
341 555
        return isset($functions[$functionName]);
342
    }
343
344
    /**
345
     * Searches for classes in the given AST
346
     *
347
     * @return array|ReflectionClass[]
348
     */
349 8403
    private function findClasses()
350
    {
351 8403
        $classes       = array();
352 8403
        $namespaceName = $this->getName();
353
        // classes can be only top-level nodes in the namespace, so we can scan them directly
354 8403
        foreach ($this->namespaceNode->stmts as $namespaceLevelNode) {
355 8403
            if ($namespaceLevelNode instanceof ClassLike) {
356 8403
                $classShortName = $namespaceLevelNode->name;
357 8403
                $className = $namespaceName ? $namespaceName .'\\' . $classShortName : $classShortName;
358
359 8403
                $namespaceLevelNode->setAttribute('fileName', $this->fileName);
360 8403
                $classes[$className] = new ReflectionClass($className, $namespaceLevelNode);
361
            }
362
        }
363
364 8403
        return $classes;
365
    }
366
367
    /**
368
     * Searches for functions in the given AST
369
     *
370
     * @return array
371
     */
372 558
    private function findFunctions()
373
    {
374 558
        $functions     = array();
375 558
        $namespaceName = $this->getName();
376
377
        // functions can be only top-level nodes in the namespace, so we can scan them directly
378 558
        foreach ($this->namespaceNode->stmts as $namespaceLevelNode) {
379 558
            if ($namespaceLevelNode instanceof Function_) {
380 558
                $funcShortName = $namespaceLevelNode->name;
381 558
                $functionName  = $namespaceName ? $namespaceName .'\\' . $funcShortName : $funcShortName;
382
383 558
                $namespaceLevelNode->setAttribute('fileName', $this->fileName);
384 558
                $functions[$funcShortName] = new ReflectionFunction($functionName, $namespaceLevelNode);
385
            }
386
        }
387
388 558
        return $functions;
389
    }
390
391
    /**
392
     * Searches for constants in the given AST
393
     *
394
     * @param bool $withDefined Include constants defined via "define(...)" in results.
395
     *
396
     * @return array
397
     */
398 40
    private function findConstants($withDefined = false)
399
    {
400 40
        $constants        = array();
401 40
        $expressionSolver = new NodeExpressionResolver($this);
402
403
        // constants can be only top-level nodes in the namespace, so we can scan them directly
404 40
        foreach ($this->namespaceNode->stmts as $namespaceLevelNode) {
405 40
            if ($namespaceLevelNode instanceof Const_) {
406 33
                $nodeConstants = $namespaceLevelNode->consts;
407 33
                if (!empty($nodeConstants)) {
408 33
                    foreach ($nodeConstants as $nodeConstant) {
409 33
                        $expressionSolver->process($nodeConstant->value);
410 40
                        $constants[$nodeConstant->name] = $expressionSolver->getValue();
411
                    }
412
                }
413
            }
414
        }
415
416 40
        if ($withDefined) {
417 2
            foreach ($this->namespaceNode->stmts as $namespaceLevelNode) {
418 2
                if ($namespaceLevelNode instanceof FuncCall
419 2
                    && $namespaceLevelNode->name instanceof Name
420 2
                    && (string)$namespaceLevelNode->name === 'define'
421
                ) {
422 2
                    $expressionSolver->process($namespaceLevelNode->args[0]->value);
423 2
                    $constantName = $expressionSolver->getValue();
424
425
                    // Ignore constants, for which name can't be determined.
426 2
                    if (strlen($constantName)) {
427 2
                        $expressionSolver->process($namespaceLevelNode->args[1]->value);
428 2
                        $constantValue = $expressionSolver->getValue();
429
430 2
                        $constants[$constantName] = $constantValue;
431
                    }
432
                }
433
            }
434
        }
435
436 40
        return $constants;
437
    }
438
439
    /**
440
     * Searchse for namespace aliases for the current block
441
     *
442
     * @return array
443
     */
444 1
    private function findNamespaceAliases()
445
    {
446 1
        $namespaceAliases = [];
447
448
        // aliases can be only top-level nodes in the namespace, so we can scan them directly
449 1
        foreach ($this->namespaceNode->stmts as $namespaceLevelNode) {
450 1
            if ($namespaceLevelNode instanceof Use_) {
451 1
                $useAliases = $namespaceLevelNode->uses;
452 1
                if (!empty($useAliases)) {
453 1
                    foreach ($useAliases as $useNode) {
454 1
                        $namespaceAliases[$useNode->name->toString()] = $useNode->alias;
455
                    }
456
                }
457
            }
458
        }
459
460 1
        return $namespaceAliases;
461
    }
462
}
463