Total Complexity | 118 |
Total Lines | 569 |
Duplicated Lines | 0 % |
Changes | 22 | ||
Bugs | 3 | Features | 3 |
Complex classes like DependencyResolver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use DependencyResolver, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
29 | class DependencyResolver |
||
30 | { |
||
31 | /** |
||
32 | * @var Broker |
||
33 | */ |
||
34 | protected $broker; |
||
35 | |||
36 | /** |
||
37 | * @var Lexer |
||
38 | */ |
||
39 | protected $phpDocLexer; |
||
40 | |||
41 | /** |
||
42 | * @var PhpDocParser |
||
43 | */ |
||
44 | protected $phpDocParser; |
||
45 | |||
46 | /** |
||
47 | * @var DependencyGraphBuilder |
||
48 | */ |
||
49 | protected $dependencyGraphBuilder; |
||
50 | |||
51 | /** |
||
52 | * @var \ReflectionClass |
||
53 | */ |
||
54 | protected $depender = null; |
||
55 | |||
56 | public function __construct(Broker $broker, Lexer $phpDocLexer, PhpDocParser $phpDocParser) |
||
57 | { |
||
58 | $this->broker = $broker; |
||
59 | $this->phpDocLexer = $phpDocLexer; |
||
60 | $this->phpDocParser = $phpDocParser; |
||
61 | } |
||
62 | |||
63 | protected function getDependerReflection(\PhpParser\Node $node, Scope $scope): ?ClassReflection |
||
64 | { |
||
65 | if ($scope->isInClass()) { |
||
66 | return $scope->getClassReflection(); |
||
67 | } else { |
||
68 | // Maybe, class declare statement |
||
69 | // ex: |
||
70 | // class Hoge {} |
||
71 | // abstract class Hoge {} |
||
72 | // interface Hoge {} |
||
73 | if ($node instanceof \PhpParser\Node\Stmt\ClassLike) { |
||
74 | return $this->resolveClassReflection($node->namespacedName->toString()); |
||
75 | } |
||
76 | } |
||
77 | |||
78 | return null; |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * @param \PhpParser\Node $node |
||
83 | * @param Scope $scope |
||
84 | * @param DependencyGraphBuilder $dependencyGraphBuilder |
||
85 | * @return ReflectionWithFilename[] |
||
86 | */ |
||
87 | public function resolveDependencies(\PhpParser\Node $node, Scope $scope, DependencyGraphBuilder $dependencyGraphBuilder): array |
||
88 | { |
||
89 | try { |
||
90 | if (is_null($this->depender = $this->getDependerReflection($node, $scope))) { |
||
|
|||
91 | return []; |
||
92 | } |
||
93 | $this->depender = $this->depender->getNativeReflection(); |
||
94 | $this->dependencyGraphBuilder = $dependencyGraphBuilder; |
||
95 | |||
96 | if ($node instanceof \PhpParser\Node\Stmt\Class_) { |
||
97 | // define class statement |
||
98 | // ex: class SomeClass {} |
||
99 | $this->resolveClassNode($node); |
||
100 | } elseif ($node instanceof \PhpParser\Node\Stmt\Interface_) { |
||
101 | // define interface statement |
||
102 | // ex: interface SomeInterface {} |
||
103 | $this->resolveInterfaceNode($node); |
||
104 | } elseif ($node instanceof \PhpParser\Node\Stmt\ClassMethod) { |
||
105 | // define class method statement |
||
106 | // ex: |
||
107 | // class SomeClass { |
||
108 | // function ClassMethod() {} |
||
109 | // } |
||
110 | $this->resolveClassMethod($node, $scope); |
||
111 | // } elseif ($node instanceof \PhpParser\Node\Stmt\Function_) { |
||
112 | // // define function statement |
||
113 | // // ex: function SomeFunction() {} |
||
114 | // return $this->resolveFunction($node); |
||
115 | } elseif ($node instanceof \PhpParser\Node\Expr\Closure) { |
||
116 | // closure expression |
||
117 | // ex: |
||
118 | // function (SomeClass1 $someClass1): SomeClass2 { |
||
119 | // // some logic. |
||
120 | // } |
||
121 | $this->resolveClosure($node, $scope); |
||
122 | } elseif ($node instanceof \PhpParser\Node\Expr\FuncCall) { |
||
123 | // function call expression |
||
124 | // ex: someFunction(); |
||
125 | $this->resolveFuncCall($node, $scope); |
||
126 | } elseif ($node instanceof \PhpParser\Node\Expr\MethodCall) { |
||
127 | // method call expression |
||
128 | // ex: $someObject->someMethod(); |
||
129 | $this->resolveMethodCall($node, $scope); |
||
130 | } elseif ($node instanceof \PhpParser\Node\Expr\PropertyFetch) { |
||
131 | // property fetch expression |
||
132 | // ex: $someObject->somePublicProperty; |
||
133 | $this->resolvePropertyFetch($node, $scope); |
||
134 | } elseif ($node instanceof \PhpParser\Node\Expr\StaticCall) { |
||
135 | // static method call expression |
||
136 | // ex: SomeClass::someStaticMethod() |
||
137 | $this->resolveStaticCall($node, $scope); |
||
138 | } elseif ($node instanceof \PhpParser\Node\Expr\ClassConstFetch) { |
||
139 | // class const fetch expression |
||
140 | // ex: SomeClass::SOME_CONST |
||
141 | $this->resolveClassConstFetch($node, $scope); |
||
142 | } elseif ($node instanceof \PhpParser\Node\Expr\StaticPropertyFetch) { |
||
143 | // class static property fetch expression |
||
144 | // ex: SomeClass::$someStaticProperty |
||
145 | $this->resolveStaticPropertyFetch($node, $scope); |
||
146 | } elseif ($node instanceof \PhpParser\Node\Expr\New_) { |
||
147 | // new object expression |
||
148 | // ex: new SomeClass(); |
||
149 | $this->resolveNew($node, $scope); |
||
150 | } elseif ($node instanceof \PhpParser\Node\Stmt\TraitUse) { |
||
151 | // use trait expression |
||
152 | // ex: |
||
153 | // class SomeClass { |
||
154 | // use SomeTrait; |
||
155 | // } |
||
156 | $this->resolveTraitUse($node); |
||
157 | } elseif ($node instanceof \PhpParser\Node\Expr\Instanceof_) { |
||
158 | // instanceof expression |
||
159 | // ex: if ($someObject instanceof SomeClass) {} |
||
160 | $this->resolveInstanceOf($node, $scope); |
||
161 | } elseif ($node instanceof \PhpParser\Node\Stmt\Catch_) { |
||
162 | // catch expression |
||
163 | // ex: try {} catch (SomeException $e) {} |
||
164 | $this->resolveCatch($node, $scope); |
||
165 | } elseif ($node instanceof \PhpParser\Node\Expr\ArrayDimFetch) { |
||
166 | // array dim fetch expression |
||
167 | // ex: $someArray[2] |
||
168 | $this->resolveArrayDimFetch($node, $scope); |
||
169 | } elseif ($node instanceof \PhpParser\Node\Stmt\Foreach_) { |
||
170 | // foreach access expression |
||
171 | // foreach ($someArray as $key => $someObject) {} |
||
172 | $this->resolveForeach($node, $scope); |
||
173 | } elseif ($node instanceof \PhpParser\Node\Expr\Array_) { |
||
174 | $this->resolveArray($node, $scope); |
||
175 | } elseif ($node instanceof \PhpParser\Node\Stmt\PropertyProperty) { |
||
176 | // property with phpdoc |
||
177 | // ex: |
||
178 | // class SomeClass { |
||
179 | // /** @var SomeClass2 $someProperty */ |
||
180 | // private $someProperty; |
||
181 | // } |
||
182 | $this->resolvePropertyProperty($node, $scope); |
||
183 | } |
||
184 | } catch (AnalyzedCodeException $e) { |
||
185 | throw new ResolveDependencyException($node, 'resolving node dependency is failed.', 0, $e); |
||
186 | } catch (ShouldNotHappenException $e) { |
||
187 | throw new ResolveDependencyException($node, 'resolving node dependency is failed.', 0, $e); |
||
188 | } |
||
189 | |||
190 | return []; |
||
191 | } |
||
192 | |||
193 | public function resolveClassReflection(string $className): ?ClassReflection |
||
194 | { |
||
195 | try { |
||
196 | return $this->broker->getClass($className); |
||
197 | } catch (\PHPStan\Broker\ClassNotFoundException $e) { |
||
198 | return null; |
||
199 | // return new UnknownClassReflection($className); |
||
200 | } |
||
201 | } |
||
202 | |||
203 | protected function getFunctionReflection(\PhpParser\Node\Name $nameNode, ?Scope $scope): ReflectionWithFilename |
||
204 | { |
||
205 | $reflection = $this->broker->getFunction($nameNode, $scope); |
||
206 | if (!$reflection instanceof ReflectionWithFilename) { |
||
207 | throw new \PHPStan\Broker\FunctionNotFoundException((string) $nameNode); |
||
208 | } |
||
209 | |||
210 | return $reflection; |
||
211 | } |
||
212 | |||
213 | /** |
||
214 | * @param ParametersAcceptorWithPhpDocs $parametersAcceptor |
||
215 | * @return ClassReflection[] |
||
216 | */ |
||
217 | protected function extractFromParametersAcceptor(ParametersAcceptorWithPhpDocs $parametersAcceptor): array |
||
218 | { |
||
219 | $dependenciesReflections = []; |
||
220 | |||
221 | foreach ($parametersAcceptor->getParameters() as $parameter) { |
||
222 | $referencedClasses = array_merge( |
||
223 | $parameter->getNativeType()->getReferencedClasses(), |
||
224 | $parameter->getPhpDocType()->getReferencedClasses() |
||
225 | ); |
||
226 | |||
227 | foreach ($referencedClasses as $referencedClass) { |
||
228 | $dependenciesReflections[] = $this->resolveClassReflection($referencedClass); |
||
229 | } |
||
230 | } |
||
231 | |||
232 | $returnTypeReferencedClasses = array_merge( |
||
233 | $parametersAcceptor->getNativeReturnType()->getReferencedClasses(), |
||
234 | $parametersAcceptor->getPhpDocReturnType()->getReferencedClasses() |
||
235 | ); |
||
236 | foreach ($returnTypeReferencedClasses as $referencedClass) { |
||
237 | $dependenciesReflections[] = $this->resolveClassReflection($referencedClass); |
||
238 | } |
||
239 | |||
240 | return $dependenciesReflections; |
||
241 | } |
||
242 | |||
243 | protected function resolveClassReflectionOrAddUnkownDependency(string $className): ?ClassReflection |
||
244 | { |
||
245 | if (!is_null($classReflection = $this->resolveClassReflection($className))) { |
||
246 | return $classReflection; |
||
247 | } |
||
248 | |||
249 | $this->dependencyGraphBuilder->addUnknownDependency($this->depender, $className); |
||
250 | return null; |
||
251 | } |
||
252 | |||
253 | protected function addDependencyWhenResolveClassReflectionIsSucceeded(string $className): void |
||
254 | { |
||
255 | if ($dependee = $this->resolveClassReflectionOrAddUnkownDependency($className)) { |
||
256 | $this->dependencyGraphBuilder->addDependency($this->depender, $dependee->getNativeReflection()); |
||
257 | } |
||
258 | } |
||
259 | |||
260 | protected function resolveClassNode(\PhpParser\Node\Stmt\Class_ $node): void |
||
261 | { |
||
262 | if ($node->extends !== null) { |
||
263 | if ($dependee = $this->resolveClassReflectionOrAddUnkownDependency($node->extends->toString())) { |
||
264 | $this->dependencyGraphBuilder->addExtends($this->depender, $dependee->getNativeReflection()); |
||
265 | } |
||
266 | } |
||
267 | foreach ($node->implements as $className) { |
||
268 | if ($dependee = $this->resolveClassReflectionOrAddUnkownDependency($className->toString())) { |
||
269 | $this->dependencyGraphBuilder->addImplements($this->depender, $dependee->getNativeReflection()); |
||
270 | } |
||
271 | } |
||
272 | if ($node->getDocComment() !== null) { |
||
273 | $tokens = new TokenIterator($this->phpDocLexer->tokenize($node->getDocComment()->getText())); |
||
274 | $phpDocNode = $this->phpDocParser->parse($tokens); |
||
275 | foreach ($phpDocNode->getTagsByName('@dependOn') as $phpDocTagNode) { |
||
276 | /** @var PhpDocTagNode $phpDocTagNode */ |
||
277 | preg_match('/^@dependOn\s+(.+)$/', $phpDocTagNode->__toString(), $matches); |
||
278 | |||
279 | $this->addDependencyWhenResolveClassReflectionIsSucceeded($matches[1]); |
||
280 | }; |
||
281 | } |
||
282 | } |
||
283 | |||
284 | protected function resolveInterfaceNode(\PhpParser\Node\Stmt\Interface_ $node): void |
||
285 | { |
||
286 | if ($node->extends !== null) { |
||
287 | foreach ($node->extends as $className) { |
||
288 | if ($dependee = $this->resolveClassReflection($className->toString())) { |
||
289 | $this->dependencyGraphBuilder->addExtends($this->depender, $dependee->getNativeReflection()); |
||
290 | } |
||
291 | } |
||
292 | } |
||
293 | } |
||
294 | |||
295 | /** |
||
296 | * @param \PhpParser\Node\Stmt\ClassMethod $node |
||
297 | * @param Scope $scope |
||
298 | * @throws \PHPStan\Reflection\MissingMethodFromReflectionException |
||
299 | */ |
||
300 | protected function resolveClassMethod(\PhpParser\Node\Stmt\ClassMethod $node, Scope $scope) |
||
301 | { |
||
302 | if (!$scope->isInClass()) { |
||
303 | throw new \PHPStan\ShouldNotHappenException(); |
||
304 | } |
||
305 | |||
306 | $nativeMethod = $scope->getClassReflection()->getNativeMethod($node->name->name); |
||
307 | if ($nativeMethod instanceof PhpMethodReflection) { |
||
308 | /** @var \PHPStan\Reflection\ParametersAcceptorWithPhpDocs $parametersAcceptor */ |
||
309 | $parametersAcceptor = ParametersAcceptorSelector::selectSingle($nativeMethod->getVariants()); |
||
310 | |||
311 | // foreach ($this->extractFromParametersAcceptor($parametersAcceptor) as $classReflection) { |
||
312 | // $this->dependencyGraphBuilder->addDependency($this->depender, $classReflection->getNativeReflection()); |
||
313 | // } |
||
314 | |||
315 | foreach ($parametersAcceptor->getParameters() as $parameter) { |
||
316 | $referencedClasses = array_merge( |
||
317 | $parameter->getNativeType()->getReferencedClasses(), |
||
318 | $parameter->getPhpDocType()->getReferencedClasses() |
||
319 | ); |
||
320 | |||
321 | foreach ($referencedClasses as $referencedClass) { |
||
322 | $this->addDependencyWhenResolveClassReflectionIsSucceeded($referencedClass); |
||
323 | } |
||
324 | } |
||
325 | |||
326 | $returnTypeReferencedClasses = array_merge( |
||
327 | $parametersAcceptor->getNativeReturnType()->getReferencedClasses(), |
||
328 | $parametersAcceptor->getPhpDocReturnType()->getReferencedClasses() |
||
329 | ); |
||
330 | foreach ($returnTypeReferencedClasses as $referencedClass) { |
||
331 | $this->addDependencyWhenResolveClassReflectionIsSucceeded($referencedClass); |
||
332 | } |
||
333 | } |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * @param \PhpParser\Node\Stmt\Function_ $node |
||
338 | * @return ReflectionWithFilename[] |
||
339 | * @throws \PHPStan\Broker\FunctionNotFoundException |
||
340 | */ |
||
341 | protected function resolveFunction(\PhpParser\Node\Stmt\Function_ $node): array |
||
342 | { |
||
343 | $functionName = $node->name->name; |
||
344 | if (isset($node->namespacedName)) { |
||
345 | $functionName = (string)$node->namespacedName; |
||
346 | } |
||
347 | $functionNameName = new \PhpParser\Node\Name($functionName); |
||
348 | if ($this->broker->hasCustomFunction($functionNameName, null)) { |
||
349 | $functionReflection = $this->broker->getCustomFunction($functionNameName, null); |
||
350 | |||
351 | /** @var \PHPStan\Reflection\ParametersAcceptorWithPhpDocs $parametersAcceptor */ |
||
352 | $parametersAcceptor = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants()); |
||
353 | return $this->extractFromParametersAcceptor($parametersAcceptor); |
||
354 | } |
||
355 | |||
356 | return []; |
||
357 | } |
||
358 | |||
359 | protected function resolveClosure(\PhpParser\Node\Expr\Closure $node, Scope $scope) |
||
360 | { |
||
361 | /** @var ClosureType $closureType */ |
||
362 | $closureType = $scope->getType($node); |
||
363 | foreach ($closureType->getParameters() as $parameter) { |
||
364 | $referencedClasses = $parameter->getType()->getReferencedClasses(); |
||
365 | foreach ($referencedClasses as $referencedClass) { |
||
366 | $this->addDependencyWhenResolveClassReflectionIsSucceeded($referencedClass); |
||
367 | } |
||
368 | } |
||
369 | |||
370 | $returnTypeReferencedClasses = $closureType->getReturnType()->getReferencedClasses(); |
||
371 | foreach ($returnTypeReferencedClasses as $referencedClass) { |
||
372 | $this->addDependencyWhenResolveClassReflectionIsSucceeded($referencedClass); |
||
373 | } |
||
374 | } |
||
375 | |||
376 | protected function resolveFuncCall(\PhpParser\Node\Expr\FuncCall $node, Scope $scope) |
||
377 | { |
||
378 | // $functionName = $node->name; |
||
379 | // if ($functionName instanceof \PhpParser\Node\Name) { |
||
380 | // try { |
||
381 | // $dependenciesReflections[] = $this->getFunctionReflection($functionName, $scope); |
||
382 | // } catch (\PHPStan\Broker\FunctionNotFoundException $e) { |
||
383 | // // pass |
||
384 | // } |
||
385 | // } else { |
||
386 | // $variants = $scope->getType($functionName)->getCallableParametersAcceptors($scope); |
||
387 | // foreach ($variants as $variant) { |
||
388 | // $referencedClasses = $variant->getReturnType()->getReferencedClasses(); |
||
389 | // foreach ($referencedClasses as $referencedClass) { |
||
390 | // $dependenciesReflections[] = $this->resolveClassReflection($referencedClass); |
||
391 | // } |
||
392 | // } |
||
393 | // } |
||
394 | |||
395 | $returnType = $scope->getType($node); |
||
396 | foreach ($returnType->getReferencedClasses() as $referencedClass) { |
||
397 | $this->addDependencyWhenResolveClassReflectionIsSucceeded($referencedClass); |
||
398 | } |
||
399 | } |
||
400 | |||
401 | protected function resolveMethodCall(\PhpParser\Node\Expr\MethodCall $node, Scope $scope) |
||
402 | { |
||
403 | if ($node instanceof Identifier) { |
||
404 | $classNames = $scope->getType($node->var)->getReferencedClasses(); |
||
405 | foreach ($classNames as $className) { |
||
406 | if ($dependee = $this->resolveClassReflectionOrAddUnkownDependency($className)) { |
||
407 | $this->dependencyGraphBuilder->addMethodCall( |
||
408 | $this->depender, |
||
409 | $dependee->getNativeReflection(), |
||
410 | $node->name->toString(), |
||
411 | $scope->getFunctionName() |
||
412 | ); |
||
413 | } |
||
414 | } |
||
415 | } |
||
416 | |||
417 | $returnType = $scope->getType($node); |
||
418 | foreach ($returnType->getReferencedClasses() as $referencedClass) { |
||
419 | $this->addDependencyWhenResolveClassReflectionIsSucceeded($referencedClass); |
||
420 | } |
||
421 | } |
||
422 | |||
423 | protected function resolvePropertyFetch(\PhpParser\Node\Expr\PropertyFetch $node, Scope $scope) |
||
424 | { |
||
425 | if ($node->name instanceof Identifier) { |
||
426 | $classNames = $scope->getType($node->var)->getReferencedClasses(); |
||
427 | foreach ($classNames as $className) { |
||
428 | if ($dependee = $this->resolveClassReflectionOrAddUnkownDependency($className)) { |
||
429 | $this->dependencyGraphBuilder->addPropertyFetch( |
||
430 | $this->depender, |
||
431 | $dependee->getNativeReflection(), |
||
432 | $node->name->toString(), |
||
433 | $scope->getFunctionName() |
||
434 | ); |
||
435 | } |
||
436 | } |
||
437 | } |
||
438 | |||
439 | $returnType = $scope->getType($node); |
||
440 | foreach ($returnType->getReferencedClasses() as $referencedClass) { |
||
441 | $this->addDependencyWhenResolveClassReflectionIsSucceeded($referencedClass); |
||
442 | } |
||
443 | } |
||
444 | |||
445 | protected function resolveStaticCall(\PhpParser\Node\Expr\StaticCall $node, Scope $scope) |
||
446 | { |
||
447 | if ($node->name instanceof Identifier) { |
||
448 | if ($node->class instanceof \PhpParser\Node\Name) { |
||
449 | if ($dependee = $this->resolveClassReflectionOrAddUnkownDependency($scope->resolveName($node->class))) { |
||
450 | $this->dependencyGraphBuilder->addMethodCall($this->depender, $dependee->getNativeReflection(), $node->name->toString(), $scope->getFunctionName()); |
||
451 | } |
||
452 | } else { |
||
453 | foreach ($scope->getType($node->class)->getReferencedClasses() as $referencedClass) { |
||
454 | if ($dependee = $this->resolveClassReflectionOrAddUnkownDependency($referencedClass)) { |
||
455 | $this->dependencyGraphBuilder->addMethodCall($this->depender, $dependee->getNativeReflection(), $node->name->toString(), $scope->getFunctionName()); |
||
456 | } |
||
457 | } |
||
458 | } |
||
459 | } |
||
460 | |||
461 | $returnType = $scope->getType($node); |
||
462 | foreach ($returnType->getReferencedClasses() as $referencedClass) { |
||
463 | $this->addDependencyWhenResolveClassReflectionIsSucceeded($referencedClass); |
||
464 | } |
||
465 | } |
||
466 | |||
467 | protected function resolveClassConstFetch(\PhpParser\Node\Expr\ClassConstFetch $node, Scope $scope) |
||
468 | { |
||
469 | if ($node->class instanceof \PhpParser\Node\Name) { |
||
470 | if ($dependee = $this->resolveClassReflectionOrAddUnkownDependency($scope->resolveName($node->class))) { |
||
471 | $this->dependencyGraphBuilder->addConstFetch( |
||
472 | $this->depender, |
||
473 | $dependee->getNativeReflection(), |
||
474 | $node->name->toString(), |
||
475 | $scope->getFunctionName() |
||
476 | ); |
||
477 | } |
||
478 | } else { |
||
479 | foreach ($scope->getType($node->class)->getReferencedClasses() as $referencedClass) { |
||
480 | if ($dependee = $this->resolveClassReflectionOrAddUnkownDependency($referencedClass)) { |
||
481 | $this->dependencyGraphBuilder->addConstFetch($this->depender, $dependee->getNativeReflection(), $node->name->toString(), $scope->getFunctionName()); |
||
482 | } |
||
483 | } |
||
484 | } |
||
485 | |||
486 | $returnType = $scope->getType($node); |
||
487 | foreach ($returnType->getReferencedClasses() as $referencedClass) { |
||
488 | $this->addDependencyWhenResolveClassReflectionIsSucceeded($referencedClass); |
||
489 | } |
||
490 | } |
||
491 | |||
492 | protected function resolveStaticPropertyFetch(\PhpParser\Node\Expr\StaticPropertyFetch $node, Scope $scope) |
||
493 | { |
||
494 | if ($node->class instanceof \PhpParser\Node\Name) { |
||
495 | if ($dependee = $this->resolveClassReflectionOrAddUnkownDependency($scope->resolveName($node->class))) { |
||
496 | $this->dependencyGraphBuilder->addPropertyFetch($this->depender, $dependee->getNativeReflection(), $node->name->toString(), $scope->getFunctionName()); |
||
497 | } |
||
498 | } else { |
||
499 | foreach ($scope->getType($node->class)->getReferencedClasses() as $referencedClass) { |
||
500 | if ($dependee = $this->resolveClassReflectionOrAddUnkownDependency($scope->resolveName($node->class))) { |
||
501 | $this->dependencyGraphBuilder->addPropertyFetch($this->depender, $dependee->getNativeReflection(), $node->name->toString(), $scope->getFunctionName()); |
||
502 | } |
||
503 | } |
||
504 | } |
||
505 | |||
506 | $returnType = $scope->getType($node); |
||
507 | foreach ($returnType->getReferencedClasses() as $referencedClass) { |
||
508 | $this->addDependencyWhenResolveClassReflectionIsSucceeded($referencedClass); |
||
509 | } |
||
510 | } |
||
511 | |||
512 | protected function resolveNew(\PhpParser\Node\Expr\New_ $node, Scope $scope) |
||
513 | { |
||
514 | if ($node->class instanceof \PhpParser\Node\Name) { |
||
515 | if ($dependee = $this->resolveClassReflectionOrAddUnkownDependency($scope->resolveName($node->class))) { |
||
516 | $this->dependencyGraphBuilder->addNew($this->depender, $dependee->getNativeReflection(), $scope->getFunctionName()); |
||
517 | } |
||
518 | } |
||
519 | } |
||
520 | |||
521 | protected function resolveTraitUse(\PhpParser\Node\Stmt\TraitUse $node) |
||
522 | { |
||
523 | foreach ($node->traits as $traitName) { |
||
524 | if ($dependee = $this->resolveClassReflectionOrAddUnkownDependency($traitName->toString())) { |
||
525 | $this->dependencyGraphBuilder->addUseTrait($this->depender, $dependee->getNativeReflection()); |
||
526 | } |
||
527 | } |
||
528 | } |
||
529 | |||
530 | protected function resolveInstanceOf(\PhpParser\Node\Expr\Instanceof_ $node, Scope $scope) |
||
531 | { |
||
532 | if ($node->class instanceof \PhpParser\Node\Name) { |
||
533 | $this->addDependencyWhenResolveClassReflectionIsSucceeded($scope->resolveName($node->class)); |
||
534 | } |
||
535 | } |
||
536 | |||
537 | protected function resolveCatch(\PhpParser\Node\Stmt\Catch_ $node, Scope $scope) |
||
538 | { |
||
539 | foreach ($node->types as $type) { |
||
540 | $this->addDependencyWhenResolveClassReflectionIsSucceeded($scope->resolveName($type)); |
||
541 | } |
||
542 | } |
||
543 | |||
544 | protected function resolveArrayDimFetch(\PhpParser\Node\Expr\ArrayDimFetch $node, Scope $scope) |
||
545 | { |
||
546 | if ($node->dim !== null) { |
||
547 | $varType = $scope->getType($node->var); |
||
548 | $dimType = $scope->getType($node->dim); |
||
549 | |||
550 | foreach ($varType->getOffsetValueType($dimType)->getReferencedClasses() as $referencedClass) { |
||
551 | $this->addDependencyWhenResolveClassReflectionIsSucceeded($referencedClass); |
||
552 | } |
||
553 | } |
||
554 | } |
||
555 | |||
556 | protected function resolveForeach(\PhpParser\Node\Stmt\Foreach_ $node, Scope $scope) |
||
567 | } |
||
568 | } |
||
569 | |||
570 | protected function resolveArray(\PhpParser\Node\Expr\Array_ $node, Scope $scope) |
||
571 | { |
||
572 | $arrayType = $scope->getType($node); |
||
573 | if (!$arrayType->isCallable()->no()) { |
||
574 | foreach ($arrayType->getCallableParametersAcceptors($scope) as $variant) { |
||
575 | $referencedClasses = $variant->getReturnType()->getReferencedClasses(); |
||
576 | foreach ($referencedClasses as $referencedClass) { |
||
577 | $this->addDependencyWhenResolveClassReflectionIsSucceeded($referencedClass); |
||
578 | } |
||
579 | } |
||
580 | } |
||
581 | } |
||
582 | |||
583 | /** |
||
584 | * @param \PhpParser\Node\Stmt\PropertyProperty $node |
||
585 | * @param Scope $scope |
||
586 | * @throws \PHPStan\Reflection\MissingPropertyFromReflectionException |
||
587 | */ |
||
588 | protected function resolvePropertyProperty(\PhpParser\Node\Stmt\PropertyProperty $node, Scope $scope) |
||
598 | } |
||
599 | } |
||
600 | } |
||
601 | } |
||
602 |
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.