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
|
|||
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 |
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 theid
property of an instance of theAccount
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.