Completed
Pull Request — master (#107)
by Alexander
03:29
created

ReflectionFileNamespace::getNode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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