add ArgumentReplacerItemRecipe, use typed config structure

This commit is contained in:
TomasVotruba 2018-02-13 19:48:43 +01:00
parent 3e8644fec1
commit e08aea0139
3 changed files with 179 additions and 50 deletions

View File

@ -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);
}
}
}

View File

@ -0,0 +1,107 @@
<?php declare(strict_types=1);
namespace Rector\Rector\Dynamic\Configuration;
use Webmozart\Assert\Assert;
final class ArgumentReplacerItemRecipe
{
/**
* @var string
*/
private $class;
/**
* @var string
*/
private $method;
/**
* @var int
*/
private $position;
/**
* @var string
*/
private $type;
/**
* @var mixed
*/
private $defaultValue;
/**
* @var string[]
*/
private $replaceMap = [];
/**
* @param mixed $defaultValue
* @param string[] $replaceMap
*/
private function __construct(string $class, string $method, int $position, string $type, $defaultValue = null, array $replaceMap)
{
$this->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;
}
}

View File

@ -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