Completed
Push — master ( d36faf...a71d04 )
by Alexander
22s
created

ReflectionFileNamespace::findConstants()   C

Complexity

Conditions 12
Paths 10

Size

Total Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 42
ccs 24
cts 24
cp 1
rs 6.9666
c 0
b 0
f 0
cc 12
nc 10
nop 1
crap 12

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