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

ReflectionFileNamespace   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 410
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 53
lcom 1
cbo 15
dl 0
loc 410
ccs 119
cts 119
cp 1
rs 6.8582
c 0
b 0
f 0

20 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 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\Expr\FuncCall;
16
use PhpParser\Node\Name;
17
use PhpParser\Node\Stmt\ClassLike;
18
use PhpParser\Node\Stmt\Const_;
19
use PhpParser\Node\Stmt\Function_;
20
use PhpParser\Node\Stmt\Namespace_;
21
use PhpParser\Node\Stmt\Use_;
22
23
/**
24
 * AST-based reflection for the concrete namespace in the file
25
 */
26
class ReflectionFileNamespace
27
{
28
    /**
29
     * List of classes in the namespace
30
     *
31
     * @var array|ReflectionClass[]
32
     */
33
    protected $fileClasses;
34
35
    /**
36
     * List of functions in the namespace
37
     *
38
     * @var array|ReflectionFunction[]
39
     */
40
    protected $fileFunctions;
41
42
    /**
43
     * List of constants in the namespace
44
     *
45
     * @var array
46
     */
47
    protected $fileConstants;
48
49
    /**
50
     * List of constants in the namespace including defined via "define(...)"
51
     *
52
     * @var array
53
     */
54
    protected $fileConstantsWithDefined;
55
56
    /**
57
     * List of imported namespaces (aliases)
58
     *
59
     * @var array
60
     */
61
    protected $fileNamespaceAliases;
62
63
    /**
64
     * Namespace node
65
     *
66
     * @var Namespace_
67
     */
68
    private $namespaceNode;
69
70
    /**
71
     * Name of the file
72
     *
73
     * @var string
74
     */
75
    private $fileName;
76
77
    /**
78
     * File namespace constructor
79
     *
80
     * @param string          $fileName      Name of the file
81
     * @param string          $namespaceName Name of the namespace
82
     * @param Namespace_|null $namespaceNode Optional AST-node for this namespace block
83
     */
84 3037
    public function __construct($fileName, $namespaceName, Namespace_ $namespaceNode = null)
85
    {
86 3037
        if (!is_string($fileName)) {
87 2
            throw new \InvalidArgumentException(
88 2
                sprintf(
89 2
                    '$fileName must be a string, but a %s was passed',
90 2
                    gettype($fileName)
91
                )
92
            );
93
        }
94 3037
        $fileName = PathResolver::realpath($fileName);
95 3037
        if (!$namespaceNode) {
96 23
            $namespaceNode = ReflectionEngine::parseFileNamespace($fileName, $namespaceName);
97
        }
98 3037
        $this->namespaceNode = $namespaceNode;
99 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...
100 3037
    }
101
102
    /**
103
     * Returns the concrete class from the file namespace or false if there is no class
104
     *
105
     * @param string $className
106
     *
107
     * @return bool|ReflectionClass
108
     */
109 3001
    public function getClass($className)
110
    {
111 3001
        if ($this->hasClass($className)) {
112 3001
            return $this->fileClasses[$className];
113
        }
114
115 10
        return false;
116
    }
117
118
    /**
119
     * Gets list of classes in the namespace
120
     *
121
     * @return ReflectionClass[]|array
122
     */
123 3004
    public function getClasses()
124
    {
125 3004
        if (!isset($this->fileClasses)) {
126 3004
            $this->fileClasses = $this->findClasses();
127
        }
128
129 3004
        return $this->fileClasses;
130
    }
131
132
    /**
133
     * Returns a value for the constant
134
     *
135
     * @param string $constantName name of the constant to fetch
136
     *
137
     * @return bool|mixed
138
     */
139 14
    public function getConstant($constantName)
140
    {
141 14
        if ($this->hasConstant($constantName)) {
142 14
            return $this->fileConstants[$constantName];
143
        }
144
145 2
        return false;
146
    }
147
148
    /**
149
     * Returns a list of defined constants in the namespace
150
     *
151
     * @param bool $withDefined Include constants defined via "define(...)" in results.
152
     *
153
     * @return array
154
     */
155 26
    public function getConstants($withDefined = false)
156
    {
157 26
        if ($withDefined) {
158 2
            if (!isset($this->fileConstantsWithDefined)) {
159 2
                $this->fileConstantsWithDefined = $this->findConstants(true);
160
            }
161
162 2
            return $this->fileConstantsWithDefined;
163
        }
164
165 26
        if (!isset($this->fileConstants)) {
166 26
            $this->fileConstants = $this->findConstants();
167
        }
168
169 26
        return $this->fileConstants;
170
    }
171
172
    /**
173
     * Gets doc comments from a class.
174
     *
175
     * @return string|false The doc comment if it exists, otherwise "false"
176
     */
177 1
    public function getDocComment()
178
    {
179 1
        $docComment = false;
180 1
        $comments   = $this->namespaceNode->getAttribute('comments');
181
182 1
        if ($comments) {
183 1
            $docComment = (string)$comments[0];
184
        }
185
186 1
        return $docComment;
187
    }
188
189
    /**
190
     * Gets starting line number
191
     *
192
     * @return integer
193
     */
194 1
    public function getEndLine()
195
    {
196 1
        return $this->namespaceNode->getAttribute('endLine');
197
    }
198
199
    /**
200
     * Returns the name of file
201
     *
202
     * @return string
203
     */
204 5
    public function getFileName()
205
    {
206 5
        return $this->fileName;
207
    }
208
209
    /**
210
     * Returns the concrete function from the file namespace or false if there is no function
211
     *
212
     * @param string $functionName
213
     *
214
     * @return bool|ReflectionFunction
215
     */
216 9
    public function getFunction($functionName)
217
    {
218 9
        if ($this->hasFunction($functionName)) {
219 9
            return $this->fileFunctions[$functionName];
220
        }
221
222 1
        return false;
223
    }
224
225
    /**
226
     * Gets list of functions in the namespace
227
     *
228
     * @return ReflectionFunction[]|array
229
     */
230 17
    public function getFunctions()
231
    {
232 17
        if (!isset($this->fileFunctions)) {
233 17
            $this->fileFunctions = $this->findFunctions();
234
        }
235
236 17
        return $this->fileFunctions;
237
    }
238
239
    /**
240
     * Gets namespace name
241
     *
242
     * @return string
243
     */
244 3026
    public function getName()
245
    {
246 3026
        $nameNode = $this->namespaceNode->name;
247
248 3026
        return $nameNode ? $nameNode->toString() : '';
249
    }
250
251
    /**
252
     * Returns a list of namespace aliases
253
     *
254
     * @return array
255
     */
256 1
    public function getNamespaceAliases()
257
    {
258 1
        if (!isset($this->fileNamespaceAliases)) {
259 1
            $this->fileNamespaceAliases = $this->findNamespaceAliases();
260
        }
261
262 1
        return $this->fileNamespaceAliases;
263
    }
264
265
    /**
266
     * Gets starting line number
267
     *
268
     * @return integer
269
     */
270 1
    public function getStartLine()
271
    {
272 1
        return $this->namespaceNode->getAttribute('startLine');
273
    }
274
275
    /**
276
     * Checks if the given class is present in this filenamespace
277
     *
278
     * @param string $className
279
     *
280
     * @return bool
281
     */
282 3002
    public function hasClass($className)
283
    {
284 3002
        $classes = $this->getClasses();
285
286 3002
        return isset($classes[$className]);
287
    }
288
289
    /**
290
     * Checks if the given constant is present in this filenamespace
291
     *
292
     * @param string $constantName
293
     *
294
     * @return bool
295
     */
296 26
    public function hasConstant($constantName)
297
    {
298 26
        $constants = $this->getConstants();
299
300 26
        return isset($constants[$constantName]);
301
    }
302
303
    /**
304
     * Checks if the given function is present in this filenamespace
305
     *
306
     * @param string $functionName
307
     *
308
     * @return bool
309
     */
310 10
    public function hasFunction($functionName)
311
    {
312 10
        $functions = $this->getFunctions();
313
314 10
        return isset($functions[$functionName]);
315
    }
316
317
    /**
318
     * Searches for classes in the given AST
319
     *
320
     * @return array|ReflectionClass[]
321
     */
322 3004
    private function findClasses()
323
    {
324 3004
        $classes       = array();
325 3004
        $namespaceName = $this->getName();
326
        // classes can be only top-level nodes in the namespace, so we can scan them directly
327 3004
        foreach ($this->namespaceNode->stmts as $namespaceLevelNode) {
328 3004
            if ($namespaceLevelNode instanceof ClassLike) {
329 3004
                $classShortName = $namespaceLevelNode->name;
330 3004
                $className = $namespaceName ? $namespaceName .'\\' . $classShortName : $classShortName;
331
332 3004
                $namespaceLevelNode->setAttribute('fileName', $this->fileName);
333 3004
                $classes[$className] = new ReflectionClass($className, $namespaceLevelNode);
334
            }
335
        }
336
337 3004
        return $classes;
338
    }
339
340
    /**
341
     * Searches for functions in the given AST
342
     *
343
     * @return array
344
     */
345 17
    private function findFunctions()
346
    {
347 17
        $functions     = array();
348 17
        $namespaceName = $this->getName();
349
350
        // functions can be only top-level nodes in the namespace, so we can scan them directly
351 17
        foreach ($this->namespaceNode->stmts as $namespaceLevelNode) {
352 17
            if ($namespaceLevelNode instanceof Function_) {
353 17
                $funcShortName = $namespaceLevelNode->name;
354 17
                $functionName  = $namespaceName ? $namespaceName .'\\' . $funcShortName : $funcShortName;
355
356 17
                $namespaceLevelNode->setAttribute('fileName', $this->fileName);
357 17
                $functions[$funcShortName] = new ReflectionFunction($functionName, $namespaceLevelNode);
358
            }
359
        }
360
361 17
        return $functions;
362
    }
363
364
    /**
365
     * Searches for constants in the given AST
366
     *
367
     * @param bool $withDefined Include constants defined via "define(...)" in results.
368
     *
369
     * @return array
370
     */
371 26
    private function findConstants($withDefined = false)
372
    {
373 26
        $constants        = array();
374 26
        $expressionSolver = new NodeExpressionResolver($this);
375
376
        // constants can be only top-level nodes in the namespace, so we can scan them directly
377 26
        foreach ($this->namespaceNode->stmts as $namespaceLevelNode) {
378 26
            if ($namespaceLevelNode instanceof Const_) {
379 24
                $nodeConstants = $namespaceLevelNode->consts;
380 24
                if (!empty($nodeConstants)) {
381 24
                    foreach ($nodeConstants as $nodeConstant) {
382 24
                        $expressionSolver->process($nodeConstant->value);
383 26
                        $constants[$nodeConstant->name] = $expressionSolver->getValue();
384
                    }
385
                }
386
            }
387
        }
388
389 26
        if ($withDefined) {
390 2
            foreach ($this->namespaceNode->stmts as $namespaceLevelNode) {
391 2
                if ($namespaceLevelNode instanceof FuncCall
392 2
                    && $namespaceLevelNode->name instanceof Name
393 2
                    && (string)$namespaceLevelNode->name === 'define'
394
                ) {
395 2
                    $expressionSolver->process($namespaceLevelNode->args[0]->value);
396 2
                    $constantName = $expressionSolver->getValue();
397
398
                    // Ignore constants, for which name can't be determined.
399 2
                    if (strlen($constantName)) {
400 2
                        $expressionSolver->process($namespaceLevelNode->args[1]->value);
401 2
                        $constantValue = $expressionSolver->getValue();
402
403 2
                        $constants[$constantName] = $constantValue;
404
                    }
405
                }
406
            }
407
        }
408
409 26
        return $constants;
410
    }
411
412
    /**
413
     * Searchse for namespace aliases for the current block
414
     *
415
     * @return array
416
     */
417 1
    private function findNamespaceAliases()
418
    {
419 1
        $namespaceAliases = [];
420
421
        // aliases can be only top-level nodes in the namespace, so we can scan them directly
422 1
        foreach ($this->namespaceNode->stmts as $namespaceLevelNode) {
423 1
            if ($namespaceLevelNode instanceof Use_) {
424 1
                $useAliases = $namespaceLevelNode->uses;
425 1
                if (!empty($useAliases)) {
426 1
                    foreach ($useAliases as $useNode) {
427 1
                        $namespaceAliases[$useNode->name->toString()] = $useNode->alias;
428
                    }
429
                }
430
            }
431
        }
432
433 1
        return $namespaceAliases;
434
    }
435
}
436