staticAnalyzer = $staticAnalyzer; $this->reflectionProvider = $reflectionProvider; $this->reflectionResolver = $reflectionResolver; $this->parentClassScopeResolver = $parentClassScopeResolver; } public function provideMinPhpVersion() : int { return PhpVersionFeature::INSTANCE_CALL; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Changes static call to instance call, where not useful', [new CodeSample(<<<'CODE_SAMPLE' class Something { public function doWork() { } } class Another { public function run() { return Something::doWork(); } } CODE_SAMPLE , <<<'CODE_SAMPLE' class Something { public function doWork() { } } class Another { public function run() { return (new Something)->doWork(); } } CODE_SAMPLE )]); } /** * @return array> */ public function getNodeTypes() : array { return [StaticCall::class]; } /** * @param StaticCall $node */ public function refactorWithScope(Node $node, Scope $scope) : ?Node { if ($node->name instanceof Expr) { return null; } $methodName = $this->getName($node->name); $className = $this->resolveStaticCallClassName($node); if ($methodName === null) { return null; } if ($className === null) { return null; } if ($this->shouldSkip($methodName, $className, $node, $scope)) { return null; } if ($this->isInstantiable($className, $scope)) { $new = new New_($node->class); return new MethodCall($new, $node->name, $node->args); } return null; } private function resolveStaticCallClassName(StaticCall $staticCall) : ?string { if ($staticCall->class instanceof PropertyFetch) { $objectType = $this->getType($staticCall->class); if ($objectType instanceof ObjectType) { return $objectType->getClassName(); } } return $this->getName($staticCall->class); } private function shouldSkip(string $methodName, string $className, StaticCall $staticCall, Scope $scope) : bool { if (\in_array($methodName, ObjectMagicMethods::METHOD_NAMES, \true)) { return \true; } if (!$this->reflectionProvider->hasClass($className)) { return \true; } $classReflection = $this->reflectionProvider->getClass($className); if ($classReflection->isAbstract()) { return \true; } // does the method even exist? if (!$classReflection->hasMethod($methodName)) { return \true; } $isStaticMethod = $this->staticAnalyzer->isStaticMethod($classReflection, $methodName); if ($isStaticMethod) { return \true; } $reflection = $scope->getClassReflection(); if ($reflection instanceof ClassReflection && $reflection->isSubclassOf($className)) { return \true; } $className = $this->getName($staticCall->class); if (\in_array($className, [ObjectReference::PARENT, ObjectReference::SELF, ObjectReference::STATIC], \true)) { return \true; } if ($className === 'class') { return \true; } $parentClassName = $this->parentClassScopeResolver->resolveParentClassName($scope); return $className === $parentClassName; } private function isInstantiable(string $className, Scope $scope) : bool { if (!$this->reflectionProvider->hasClass($className)) { return \false; } $methodReflection = $this->reflectionResolver->resolveMethodReflection($className, '__callStatic', $scope); if ($methodReflection instanceof MethodReflection) { return \false; } $classReflection = $this->reflectionProvider->getClass($className); $nativeReflection = $classReflection->getNativeReflection(); $reflectionMethod = $nativeReflection->getConstructor(); if (!$reflectionMethod instanceof ReflectionMethod) { return \true; } if (!$reflectionMethod->isPublic()) { return \false; } // required parameters in constructor, nothing we can do return !(bool) $reflectionMethod->getNumberOfRequiredParameters(); } }