rector/rules/Transform/Rector/FuncCall/ArgumentFuncCallToMethodCallRector.php
Tomas Votruba f3d9b9c31c Updated Rector to commit 5493b6dc13
5493b6dc13 [DX] Re-use configure() in docs + remove prefixing of ValueObjectInliner (#1410)
2021-12-06 21:12:03 +00:00

221 lines
10 KiB
PHP

<?php
declare (strict_types=1);
namespace Rector\Transform\Rector\FuncCall;
use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use Rector\Core\Contract\Rector\ConfigurableRectorInterface;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Rector\AbstractRector;
use Rector\Naming\Naming\PropertyNaming;
use Rector\Naming\ValueObject\ExpectedName;
use Rector\NodeTypeResolver\TypeAnalyzer\ArrayTypeAnalyzer;
use Rector\PostRector\Collector\PropertyToAddCollector;
use Rector\PostRector\ValueObject\PropertyMetadata;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
use Rector\Transform\Contract\ValueObject\ArgumentFuncCallToMethodCallInterface;
use Rector\Transform\ValueObject\ArgumentFuncCallToMethodCall;
use Rector\Transform\ValueObject\ArrayFuncCallToMethodCall;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use RectorPrefix20211206\Webmozart\Assert\Assert;
/**
* @see \Rector\Tests\Transform\Rector\FuncCall\ArgumentFuncCallToMethodCallRector\ArgumentFuncCallToMethodCallRectorTest
*/
final class ArgumentFuncCallToMethodCallRector extends \Rector\Core\Rector\AbstractRector implements \Rector\Core\Contract\Rector\ConfigurableRectorInterface
{
/**
* @var string
*/
public const FUNCTIONS_TO_METHOD_CALLS = 'functions_to_method_calls';
/**
* @var ArgumentFuncCallToMethodCallInterface[]
*/
private $argumentFuncCallToMethodCalls = [];
/**
* @readonly
* @var \Rector\NodeTypeResolver\TypeAnalyzer\ArrayTypeAnalyzer
*/
private $arrayTypeAnalyzer;
/**
* @readonly
* @var \Rector\Naming\Naming\PropertyNaming
*/
private $propertyNaming;
/**
* @readonly
* @var \Rector\PostRector\Collector\PropertyToAddCollector
*/
private $propertyToAddCollector;
public function __construct(\Rector\NodeTypeResolver\TypeAnalyzer\ArrayTypeAnalyzer $arrayTypeAnalyzer, \Rector\Naming\Naming\PropertyNaming $propertyNaming, \Rector\PostRector\Collector\PropertyToAddCollector $propertyToAddCollector)
{
$this->arrayTypeAnalyzer = $arrayTypeAnalyzer;
$this->propertyNaming = $propertyNaming;
$this->propertyToAddCollector = $propertyToAddCollector;
}
public function getRuleDefinition() : \Symplify\RuleDocGenerator\ValueObject\RuleDefinition
{
return new \Symplify\RuleDocGenerator\ValueObject\RuleDefinition('Move help facade-like function calls to constructor injection', [new \Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample(<<<'CODE_SAMPLE'
class SomeController
{
public function action()
{
$template = view('template.blade');
$viewFactory = view();
}
}
CODE_SAMPLE
, <<<'CODE_SAMPLE'
class SomeController
{
/**
* @var \Illuminate\Contracts\View\Factory
*/
private $viewFactory;
public function __construct(\Illuminate\Contracts\View\Factory $viewFactory)
{
$this->viewFactory = $viewFactory;
}
public function action()
{
$template = $this->viewFactory->make('template.blade');
$viewFactory = $this->viewFactory;
}
}
CODE_SAMPLE
, [new \Rector\Transform\ValueObject\ArgumentFuncCallToMethodCall('view', 'Illuminate\\Contracts\\View\\Factory', 'make')])]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes() : array
{
return [\PhpParser\Node\Expr\FuncCall::class];
}
/**
* @param FuncCall $node
*/
public function refactor(\PhpParser\Node $node) : ?\PhpParser\Node
{
if ($this->shouldSkipFuncCall($node)) {
return null;
}
/** @var Class_ $classLike */
$classLike = $this->betterNodeFinder->findParentType($node, \PhpParser\Node\Stmt\Class_::class);
foreach ($this->argumentFuncCallToMethodCalls as $argumentFuncCallToMethodCall) {
if (!$this->isName($node, $argumentFuncCallToMethodCall->getFunction())) {
continue;
}
if ($argumentFuncCallToMethodCall instanceof \Rector\Transform\ValueObject\ArgumentFuncCallToMethodCall) {
return $this->refactorFuncCallToMethodCall($argumentFuncCallToMethodCall, $classLike, $node);
}
if ($argumentFuncCallToMethodCall instanceof \Rector\Transform\ValueObject\ArrayFuncCallToMethodCall) {
return $this->refactorArrayFunctionToMethodCall($argumentFuncCallToMethodCall, $node, $classLike);
}
}
return null;
}
/**
* @param mixed[] $configuration
*/
public function configure(array $configuration) : void
{
$functionToMethodCalls = $configuration[self::FUNCTIONS_TO_METHOD_CALLS] ?? $configuration;
\RectorPrefix20211206\Webmozart\Assert\Assert::isArray($functionToMethodCalls);
\RectorPrefix20211206\Webmozart\Assert\Assert::allIsAOf($functionToMethodCalls, \Rector\Transform\Contract\ValueObject\ArgumentFuncCallToMethodCallInterface::class);
$this->argumentFuncCallToMethodCalls = $functionToMethodCalls;
}
private function shouldSkipFuncCall(\PhpParser\Node\Expr\FuncCall $funcCall) : bool
{
// we can inject only in injectable class method context
/** @var ClassMethod|null $classMethod */
$classMethod = $this->betterNodeFinder->findParentType($funcCall, \PhpParser\Node\Stmt\ClassMethod::class);
if (!$classMethod instanceof \PhpParser\Node\Stmt\ClassMethod) {
return \true;
}
return $classMethod->isStatic();
}
/**
* @return PropertyFetch|MethodCall
*/
private function refactorFuncCallToMethodCall(\Rector\Transform\ValueObject\ArgumentFuncCallToMethodCall $argumentFuncCallToMethodCall, \PhpParser\Node\Stmt\Class_ $class, \PhpParser\Node\Expr\FuncCall $funcCall) : ?\PhpParser\Node
{
$fullyQualifiedObjectType = new \Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType($argumentFuncCallToMethodCall->getClass());
$expectedName = $this->propertyNaming->getExpectedNameFromType($fullyQualifiedObjectType);
if (!$expectedName instanceof \Rector\Naming\ValueObject\ExpectedName) {
throw new \Rector\Core\Exception\ShouldNotHappenException();
}
$propertyMetadata = new \Rector\PostRector\ValueObject\PropertyMetadata($expectedName->getName(), $fullyQualifiedObjectType, \PhpParser\Node\Stmt\Class_::MODIFIER_PRIVATE);
$this->propertyToAddCollector->addPropertyToClass($class, $propertyMetadata);
$propertyFetchNode = $this->nodeFactory->createPropertyFetch('this', $expectedName->getName());
if ($funcCall->args === []) {
return $this->refactorEmptyFuncCallArgs($argumentFuncCallToMethodCall, $propertyFetchNode);
}
if ($this->isFunctionToMethodCallWithArgs($funcCall, $argumentFuncCallToMethodCall)) {
$methodName = $argumentFuncCallToMethodCall->getMethodIfArgs();
if (!\is_string($methodName)) {
throw new \Rector\Core\Exception\ShouldNotHappenException();
}
return new \PhpParser\Node\Expr\MethodCall($propertyFetchNode, $methodName, $funcCall->args);
}
return null;
}
/**
* @return PropertyFetch|MethodCall|null
*/
private function refactorArrayFunctionToMethodCall(\Rector\Transform\ValueObject\ArrayFuncCallToMethodCall $arrayFuncCallToMethodCall, \PhpParser\Node\Expr\FuncCall $funcCall, \PhpParser\Node\Stmt\Class_ $class) : ?\PhpParser\Node
{
$propertyName = $this->propertyNaming->fqnToVariableName($arrayFuncCallToMethodCall->getClass());
$propertyFetch = $this->nodeFactory->createPropertyFetch('this', $propertyName);
$fullyQualifiedObjectType = new \Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType($arrayFuncCallToMethodCall->getClass());
$propertyMetadata = new \Rector\PostRector\ValueObject\PropertyMetadata($propertyName, $fullyQualifiedObjectType, \PhpParser\Node\Stmt\Class_::MODIFIER_PRIVATE);
$this->propertyToAddCollector->addPropertyToClass($class, $propertyMetadata);
return $this->createMethodCallArrayFunctionToMethodCall($funcCall, $arrayFuncCallToMethodCall, $propertyFetch);
}
/**
* @return \PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\PropertyFetch
*/
private function refactorEmptyFuncCallArgs(\Rector\Transform\ValueObject\ArgumentFuncCallToMethodCall $argumentFuncCallToMethodCall, \PhpParser\Node\Expr\PropertyFetch $propertyFetch)
{
if ($argumentFuncCallToMethodCall->getMethodIfNoArgs() !== null) {
$methodName = $argumentFuncCallToMethodCall->getMethodIfNoArgs();
return new \PhpParser\Node\Expr\MethodCall($propertyFetch, $methodName);
}
return $propertyFetch;
}
private function isFunctionToMethodCallWithArgs(\PhpParser\Node\Expr\FuncCall $funcCall, \Rector\Transform\ValueObject\ArgumentFuncCallToMethodCall $argumentFuncCallToMethodCall) : bool
{
if ($argumentFuncCallToMethodCall->getMethodIfArgs() === null) {
return \false;
}
return \count($funcCall->args) >= 1;
}
/**
* @return PropertyFetch|MethodCall|null
*/
private function createMethodCallArrayFunctionToMethodCall(\PhpParser\Node\Expr\FuncCall $funcCall, \Rector\Transform\ValueObject\ArrayFuncCallToMethodCall $arrayFuncCallToMethodCall, \PhpParser\Node\Expr\PropertyFetch $propertyFetch) : ?\PhpParser\Node
{
if ($funcCall->args === []) {
return $propertyFetch;
}
if (!$funcCall->args[0] instanceof \PhpParser\Node\Arg) {
return null;
}
if ($this->arrayTypeAnalyzer->isArrayType($funcCall->args[0]->value)) {
return new \PhpParser\Node\Expr\MethodCall($propertyFetch, $arrayFuncCallToMethodCall->getArrayMethod(), $funcCall->args);
}
if ($arrayFuncCallToMethodCall->getNonArrayMethod() === '') {
return null;
}
return new \PhpParser\Node\Expr\MethodCall($propertyFetch, $arrayFuncCallToMethodCall->getNonArrayMethod(), $funcCall->args);
}
}