Completed
Push — master ( c4d2d4...eae51e )
by Alexander
9s
created

src/ReflectionFileNamespace.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 3003
    public function __construct($fileName, $namespaceName, Namespace_ $namespaceNode = null)
85
    {
86 3003
        $fileName = PathResolver::realpath($fileName);
87 3003
        if (!$namespaceNode) {
88 22
            $namespaceNode = ReflectionEngine::parseFileNamespace($fileName, $namespaceName);
89
        }
90 3003
        $this->namespaceNode = $namespaceNode;
91 3003
        $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 3003
    }
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 2970
    public function getClass($className)
102
    {
103 2970
        if ($this->hasClass($className)) {
104 2970
            return $this->fileClasses[$className];
105
        }
106
107 10
        return false;
108
    }
109
110
    /**
111
     * Gets list of classes in the namespace
112
     *
113
     * @return ReflectionClass[]|array
114
     */
115 2972
    public function getClasses()
116
    {
117 2972
        if (!isset($this->fileClasses)) {
118 2972
            $this->fileClasses = $this->findClasses();
119
        }
120
121 2972
        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 14
    public function getConstant($constantName)
132
    {
133 14
        if ($this->hasConstant($constantName)) {
134 14
            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 25
    public function getConstants($withDefined = false)
148
    {
149 25
        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 25
        if (!isset($this->fileConstants)) {
158 25
            $this->fileConstants = $this->findConstants();
159
        }
160
161 25
        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 17
    public function getFunctions()
223
    {
224 17
        if (!isset($this->fileFunctions)) {
225 17
            $this->fileFunctions = $this->findFunctions();
226
        }
227
228 17
        return $this->fileFunctions;
229
    }
230
231
    /**
232
     * Gets namespace name
233
     *
234
     * @return string
235
     */
236 2994
    public function getName()
237
    {
238 2994
        $nameNode = $this->namespaceNode->name;
239
240 2994
        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 2971
    public function hasClass($className)
275
    {
276 2971
        $classes = $this->getClasses();
277
278 2971
        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 25
    public function hasConstant($constantName)
289
    {
290 25
        $constants = $this->getConstants();
291
292 25
        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 2972
    private function findClasses()
315
    {
316 2972
        $classes       = array();
317 2972
        $namespaceName = $this->getName();
318
        // classes can be only top-level nodes in the namespace, so we can scan them directly
319 2972
        foreach ($this->namespaceNode->stmts as $namespaceLevelNode) {
320 2972
            if ($namespaceLevelNode instanceof ClassLike) {
321 2972
                $classShortName = $namespaceLevelNode->name;
322 2972
                $className = $namespaceName ? $namespaceName .'\\' . $classShortName : $classShortName;
323
324 2972
                $namespaceLevelNode->setAttribute('fileName', $this->fileName);
325 2972
                $classes[$className] = new ReflectionClass($className, $namespaceLevelNode);
326
            }
327
        }
328
329 2972
        return $classes;
330
    }
331
332
    /**
333
     * Searches for functions in the given AST
334
     *
335
     * @return array
336
     */
337 17
    private function findFunctions()
338
    {
339 17
        $functions     = array();
340 17
        $namespaceName = $this->getName();
341
342
        // functions can be only top-level nodes in the namespace, so we can scan them directly
343 17
        foreach ($this->namespaceNode->stmts as $namespaceLevelNode) {
344 17
            if ($namespaceLevelNode instanceof Function_) {
345 17
                $funcShortName = $namespaceLevelNode->name;
346 17
                $functionName  = $namespaceName ? $namespaceName .'\\' . $funcShortName : $funcShortName;
347
348 17
                $namespaceLevelNode->setAttribute('fileName', $this->fileName);
349 17
                $functions[$funcShortName] = new ReflectionFunction($functionName, $namespaceLevelNode);
350
            }
351
        }
352
353 17
        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 25
    private function findConstants($withDefined = false)
364
    {
365 25
        $constants        = array();
366 25
        $expressionSolver = new NodeExpressionResolver($this);
367
368
        // constants can be only top-level nodes in the namespace, so we can scan them directly
369 25
        foreach ($this->namespaceNode->stmts as $namespaceLevelNode) {
370 25
            if ($namespaceLevelNode instanceof Const_) {
371 24
                $nodeConstants = $namespaceLevelNode->consts;
372 24
                if (!empty($nodeConstants)) {
373 24
                    foreach ($nodeConstants as $nodeConstant) {
374 24
                        $expressionSolver->process($nodeConstant->value);
375 25
                        $constants[$nodeConstant->name] = $expressionSolver->getValue();
376
                    }
377
                }
378
            }
379
        }
380
381 25
        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 25
        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