Completed
Push — master ( 0d5b50...e6ea71 )
by Alexander
02:05
created

ReflectionFileNamespace::getClasses()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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