Completed
Push — master ( 34991a...adfc38 )
by Alexander
13s
created

ReflectionFileNamespace   D

Complexity

Total Complexity 55

Size/Duplication

Total Lines 436
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Test Coverage

Coverage 94.44%

Importance

Changes 0
Metric Value
wmc 55
lcom 1
cbo 16
dl 0
loc 436
ccs 119
cts 126
cp 0.9444
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 getNode() 0 4 1
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

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 3037
    public function __construct($fileName, $namespaceName, Namespace_ $namespaceNode = null)
86
    {
87 3037
        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 3037
        $fileName = PathResolver::realpath($fileName);
96 3037
        if (!$namespaceNode) {
97 23
            $namespaceNode = ReflectionEngine::parseFileNamespace($fileName, $namespaceName);
98
        }
99 3037
        $this->namespaceNode = $namespaceNode;
100 3037
        $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 3037
    }
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 3001
    public function getClass($className)
111
    {
112 3001
        if ($this->hasClass($className)) {
113 3001
            return $this->fileClasses[$className];
114
        }
115
116 10
        return false;
117
    }
118
119
    /**
120
     * Gets list of classes in the namespace
121
     *
122
     * @return ReflectionClass[]|array
123
     */
124 3004
    public function getClasses()
125
    {
126 3004
        if (!isset($this->fileClasses)) {
127 3004
            $this->fileClasses = $this->findClasses();
128
        }
129
130 3004
        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 14
    public function getConstant($constantName)
141
    {
142 14
        if ($this->hasConstant($constantName)) {
143 14
            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 26
    public function getConstants($withDefined = false)
157
    {
158 26
        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 26
        if (!isset($this->fileConstants)) {
167 26
            $this->fileConstants = $this->findConstants();
168
        }
169
170 26
        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 9
    public function getFunction($functionName)
218
    {
219 9
        if ($this->hasFunction($functionName)) {
220 9
            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 17
    public function getFunctions()
232
    {
233 17
        if (!isset($this->fileFunctions)) {
234 17
            $this->fileFunctions = $this->findFunctions();
235
        }
236
237 17
        return $this->fileFunctions;
238
    }
239
240
    /**
241
     * Gets namespace name
242
     *
243
     * @return string
244
     */
245 3026
    public function getName()
246
    {
247 3026
        $nameNode = $this->namespaceNode->name;
248
249 3026
        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
    public function getNode()
272
    {
273
        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 3002
    public function hasClass($className)
310
    {
311 3002
        $classes = $this->getClasses();
312
313 3002
        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 26
    public function hasConstant($constantName)
324
    {
325 26
        $constants = $this->getConstants();
326
327 26
        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 10
    public function hasFunction($functionName)
338
    {
339 10
        $functions = $this->getFunctions();
340
341 10
        return isset($functions[$functionName]);
342
    }
343
344
    /**
345
     * Searches for classes in the given AST
346
     *
347
     * @return array|ReflectionClass[]
348
     */
349 3004
    private function findClasses()
350
    {
351 3004
        $classes       = array();
352 3004
        $namespaceName = $this->getName();
353
        // classes can be only top-level nodes in the namespace, so we can scan them directly
354 3004
        foreach ($this->namespaceNode->stmts as $namespaceLevelNode) {
355 3004
            if ($namespaceLevelNode instanceof ClassLike) {
356 3004
                $classShortName = $namespaceLevelNode->name;
357 3004
                $className = $namespaceName ? $namespaceName .'\\' . $classShortName : $classShortName;
358
359 3004
                $namespaceLevelNode->setAttribute('fileName', $this->fileName);
360 3004
                $classes[$className] = new ReflectionClass($className, $namespaceLevelNode);
361
            }
362
        }
363
364 3004
        return $classes;
365
    }
366
367
    /**
368
     * Searches for functions in the given AST
369
     *
370
     * @return array
371
     */
372 17
    private function findFunctions()
373
    {
374 17
        $functions     = array();
375 17
        $namespaceName = $this->getName();
376
377
        // functions can be only top-level nodes in the namespace, so we can scan them directly
378 17
        foreach ($this->namespaceNode->stmts as $namespaceLevelNode) {
379 17
            if ($namespaceLevelNode instanceof Function_) {
380 17
                $funcShortName = $namespaceLevelNode->name;
381 17
                $functionName  = $namespaceName ? $namespaceName .'\\' . $funcShortName : $funcShortName;
382
383 17
                $namespaceLevelNode->setAttribute('fileName', $this->fileName);
384 17
                $functions[$funcShortName] = new ReflectionFunction($functionName, $namespaceLevelNode);
385
            }
386
        }
387
388 17
        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 26
    private function findConstants($withDefined = false)
399
    {
400 26
        $constants        = array();
401 26
        $expressionSolver = new NodeExpressionResolver($this);
402
403
        // constants can be only top-level nodes in the namespace, so we can scan them directly
404 26
        foreach ($this->namespaceNode->stmts as $namespaceLevelNode) {
405 26
            if ($namespaceLevelNode instanceof Const_) {
406 24
                $nodeConstants = $namespaceLevelNode->consts;
407 24
                if (!empty($nodeConstants)) {
408 24
                    foreach ($nodeConstants as $nodeConstant) {
409 24
                        $expressionSolver->process($nodeConstant->value);
410 26
                        $constants[$nodeConstant->name] = $expressionSolver->getValue();
411
                    }
412
                }
413
            }
414
        }
415
416 26
        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 26
        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