nodeNameResolver = $nodeNameResolver; $this->nodeTypeResolver = $nodeTypeResolver; $this->valueResolver = $valueResolver; $this->reflectionProvider = $reflectionProvider; } /** * Matches array like: "[$this, 'methodName']" → ['ClassName', 'methodName'] */ public function match(\PhpParser\Node\Expr\Array_ $array) : ?\Rector\NodeCollector\ValueObject\ArrayCallable { $arrayItems = $array->items; if (\count($arrayItems) !== 2) { return null; } if ($array->items[0] === null) { return null; } if ($array->items[1] === null) { return null; } // $this, self, static, FQN $firstItemValue = $array->items[0]->value; $secondItemValue = $array->items[1]->value; if (!$secondItemValue instanceof \PhpParser\Node\Scalar\String_) { return null; } // static ::class reference? if ($firstItemValue instanceof \PhpParser\Node\Expr\ClassConstFetch) { $calleeType = $this->resolveClassConstFetchType($firstItemValue); } else { $calleeType = $this->nodeTypeResolver->resolve($firstItemValue); } if (!$calleeType instanceof \PHPStan\Type\TypeWithClassName) { return null; } $className = $calleeType->getClassName(); $methodName = $secondItemValue->value; if ($this->isCallbackAtFunctionNames($array, ['register_shutdown_function', 'forward_static_call'])) { return null; } return new \Rector\NodeCollector\ValueObject\ArrayCallable($firstItemValue, $className, $methodName); } /** * @param string[] $functionNames */ private function isCallbackAtFunctionNames(\PhpParser\Node\Expr\Array_ $array, array $functionNames) : bool { $parentNode = $array->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PARENT_NODE); if (!$parentNode instanceof \PhpParser\Node\Arg) { return \false; } $parentParentNode = $parentNode->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::PARENT_NODE); if (!$parentParentNode instanceof \PhpParser\Node\Expr\FuncCall) { return \false; } return $this->nodeNameResolver->isNames($parentParentNode, $functionNames); } /** * @return \PHPStan\Type\MixedType|\PHPStan\Type\ObjectType */ private function resolveClassConstFetchType(\PhpParser\Node\Expr\ClassConstFetch $classConstFetch) { $classConstantReference = $this->valueResolver->getValue($classConstFetch); if ($classConstantReference === 'static') { $classConstantReference = $classConstFetch->getAttribute(\Rector\NodeTypeResolver\Node\AttributeKey::CLASS_NAME); } // non-class value if (!\is_string($classConstantReference)) { return new \PHPStan\Type\MixedType(); } if (!$this->reflectionProvider->hasClass($classConstantReference)) { return new \PHPStan\Type\MixedType(); } $classReflection = $this->reflectionProvider->getClass($classConstantReference); return new \PHPStan\Type\ObjectType($classConstantReference, null, $classReflection); } }