diff --git a/src/Rector/Dynamic/ArgumentReplacerRector.php b/src/Rector/Dynamic/ArgumentReplacerRector.php index e340b8a3236..fd220265d84 100644 --- a/src/Rector/Dynamic/ArgumentReplacerRector.php +++ b/src/Rector/Dynamic/ArgumentReplacerRector.php @@ -8,20 +8,20 @@ use PhpParser\Node; use PhpParser\Node\Arg; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\StaticCall; -use PhpParser\Node\Identifier; use PhpParser\Node\Param; use PhpParser\Node\Stmt\ClassMethod; use Rector\NodeAnalyzer\ClassMethodAnalyzer; use Rector\NodeAnalyzer\MethodCallAnalyzer; use Rector\NodeAnalyzer\StaticMethodCallAnalyzer; use Rector\Rector\AbstractRector; +use Rector\Rector\Dynamic\Configuration\ArgumentReplacerItemRecipe; final class ArgumentReplacerRector extends AbstractRector { /** - * @var mixed[] + * @var ArgumentReplacerItemRecipe[] */ - private $argumentChangesMethodAndClass = []; + private $argumentReplacerItemRecipes = []; /** * @var MethodCallAnalyzer @@ -29,9 +29,9 @@ final class ArgumentReplacerRector extends AbstractRector private $methodCallAnalyzer; /** - * @var mixed[][] + * @var argumentReplacerItemRecipe[] */ - private $activeArgumentChangesByPosition = []; + private $activeArgumentReplacerItemRecipes = []; /** * @var ClassMethodAnalyzer @@ -58,7 +58,7 @@ final class ArgumentReplacerRector extends AbstractRector StaticMethodCallAnalyzer $staticMethodCallAnalyzer, ConstExprEvaluator $constExprEvaluator ) { - $this->argumentChangesMethodAndClass = $argumentChangesByMethodAndType; + $this->loadArgumentReplacerItemRecipes($argumentChangesByMethodAndType); $this->methodCallAnalyzer = $methodCallAnalyzer; $this->classMethodAnalyzer = $classMethodAnalyzer; $this->staticMethodCallAnalyzer = $staticMethodCallAnalyzer; @@ -67,9 +67,9 @@ final class ArgumentReplacerRector extends AbstractRector public function isCandidate(Node $node): bool { - $this->activeArgumentChangesByPosition = $this->matchArgumentChanges($node); + $this->activeArgumentReplacerItemRecipes = $this->matchArgumentChanges($node); - return (bool) $this->activeArgumentChangesByPosition; + return (bool) $this->activeArgumentReplacerItemRecipes; } /** @@ -79,25 +79,23 @@ final class ArgumentReplacerRector extends AbstractRector { $argumentsOrParameters = $this->getNodeArgumentsOrParameters($node); - foreach ($this->activeArgumentChangesByPosition as $position => $argumentChange) { - $key = key($argumentChange); - $value = array_shift($argumentChange); + foreach ($this->activeArgumentReplacerItemRecipes as $argumentReplacerItemRecipe) { + // @todo constants and own methods + if ($argumentReplacerItemRecipe->getType() === 'removed') { + unset($argumentsOrParameters[$argumentReplacerItemRecipe->getPosition()]); - if ($key === '~') { - if ($value === null) { // remove argument - unset($argumentsOrParameters[$position]); - } else { // new default value - $argumentsOrParameters[$position] = BuilderHelpers::normalizeValue($value); - } - } else { - // replace old value with new one + } elseif ($argumentReplacerItemRecipe->getType() === 'changed') { + $argumentsOrParameters[$argumentReplacerItemRecipe->getPosition()] = BuilderHelpers::normalizeValue($argumentReplacerItemRecipe->getDefaultValue()); + } elseif ($argumentReplacerItemRecipe->getType() === 'replace_default_value') { /** @var Arg $argumentOrParameter */ - $argumentOrParameter = $argumentsOrParameters[$position]; - + $argumentOrParameter = $argumentsOrParameters[$argumentReplacerItemRecipe->getPosition()]; $resolvedValue = $this->constExprEvaluator->evaluateDirectly($argumentOrParameter->value); - if ($resolvedValue === $key) { - $argumentsOrParameters[$position] = BuilderHelpers::normalizeValue($value); + $replaceMap = $argumentReplacerItemRecipe->getReplaceMap(); + foreach ($replaceMap as $oldValue => $newValue) { + if ($resolvedValue === $oldValue) { + $argumentsOrParameters[$argumentReplacerItemRecipe->getPosition()] = BuilderHelpers::normalizeValue($newValue); + } } } } @@ -108,7 +106,7 @@ final class ArgumentReplacerRector extends AbstractRector } /** - * @return mixed[][] + * @return ArgumentReplacerItemRecipe[] */ private function matchArgumentChanges(Node $node): array { @@ -116,17 +114,16 @@ final class ArgumentReplacerRector extends AbstractRector return []; } - foreach ($this->argumentChangesMethodAndClass as $type => $argumentChangesByMethod) { - $methods = array_keys($argumentChangesByMethod); - if ($this->isTypeAndMethods($node, $type, $methods)) { - /** @var Identifier $identifierNode */ - $identifierNode = $node->name; + $argumentReplacerItemRecipes = []; - return $argumentChangesByMethod[$identifierNode->toString()]; + foreach ($this->argumentReplacerItemRecipes as $argumentReplacerItemRecipe) { + + if ($this->isTypeAndMethods($node, $argumentReplacerItemRecipe->getClass(), [$argumentReplacerItemRecipe->getMethod()])) { + $argumentReplacerItemRecipes[] = $argumentReplacerItemRecipe; } } - return []; + return $argumentReplacerItemRecipes; } /** @@ -173,4 +170,11 @@ final class ArgumentReplacerRector extends AbstractRector return $this->classMethodAnalyzer->isTypeAndMethods($node, $type, $methods); } + + private function loadArgumentReplacerItemRecipes(array $configurationArrays): void + { + foreach ($configurationArrays as $configurationArray) { + $this->argumentReplacerItemRecipes[] = ArgumentReplacerItemRecipe::createFromArray($configurationArray); + } + } } diff --git a/src/Rector/Dynamic/Configuration/ArgumentReplacerItemRecipe.php b/src/Rector/Dynamic/Configuration/ArgumentReplacerItemRecipe.php new file mode 100644 index 00000000000..be1877a3958 --- /dev/null +++ b/src/Rector/Dynamic/Configuration/ArgumentReplacerItemRecipe.php @@ -0,0 +1,107 @@ +class = $class; + $this->method = $method; + $this->position = $position; + $this->type = $type; + $this->defaultValue = $defaultValue; + $this->replaceMap = $replaceMap; + } + + public static function createFromArray(array $data): self + { + // @todo: make exceptions clear for end user + Assert::keyExists($data, 'class'); + Assert::keyExists($data, 'method'); + Assert::keyExists($data, 'position'); + Assert::keyExists($data, 'type'); + + if ($data['type'] === 'replace_default_value') { + Assert::keyExists($data, 'replace_map'); + } + + return new self( + $data['class'], + $data['method'], + $data['position'], + $data['type'], + $data['default_value'] ?? null, + $data['replace_map'] ?? [] + ); + } + + public function getClass(): string + { + return $this->class; + } + + public function getMethod(): string + { + return $this->method; + } + + public function getPosition(): int + { + return $this->position; + } + + public function getType(): string + { + return $this->type; + } + + /** + * @return mixed|null + */ + public function getDefaultValue() + { + return $this->defaultValue; + } + + /** + * @return string[] + */ + public function getReplaceMap(): array + { + return $this->replaceMap; + } +} diff --git a/tests/Rector/Dynamic/ArgumentReplacerRector/config/rector.yml b/tests/Rector/Dynamic/ArgumentReplacerRector/config/rector.yml index 657580e27be..6567eb6ff4e 100644 --- a/tests/Rector/Dynamic/ArgumentReplacerRector/config/rector.yml +++ b/tests/Rector/Dynamic/ArgumentReplacerRector/config/rector.yml @@ -1,23 +1,41 @@ rectors: Rector\Rector\Dynamic\ArgumentReplacerRector: - 'Symfony\Component\DependencyInjection\ContainerBuilder': - 'compile': - 0: - # added default value - '~': false - 'addCompilerPass': - 2: - # added default value - '~': 0 + # value object approach + - + class: 'Symfony\Component\DependencyInjection\ContainerBuilder' + method: 'addCompilerPass' + position: 2 + type: 'added' + default_value: 0 - 'Doctrine\ORM\Persisters\Entity\AbstractEntityInheritancePersister': - 'getSelectJoinColumnSQL': - 4: - # remove completely - '~': ~ + # added default value + - + class: 'Symfony\Component\DependencyInjection\ContainerBuilder' + method: 'compile' + type: 'changed' + position: 0 + default_value: false - 'Symfony\Component\DependencyInjection\Definition': - 'setScope': - 0: - # replace by new value - 'Symfony\Component\DependencyInjection\ContainerBuilder::SCOPE_PROTOTYPE': false + # added default value + - + class: 'Symfony\Component\DependencyInjection\ContainerBuilder' + method: 'addCompilerPass' + type: 'changed' + position: 0 + default_value: 0 + + # remove argument + - + class: 'Doctrine\ORM\Persisters\Entity\AbstractEntityInheritancePersister' + method: 'getSelectJoinColumnSQL' + position: 4 + type: 'remove' + + # replace default value + - + class: 'Symfony\Component\DependencyInjection\Definition' + method: 'setScope' + position: 0 + type: 'replace_default_value' + replace_map: + 'Symfony\Component\DependencyInjection\ContainerBuilder::SCOPE_PROTOTYPE': false