adapt rector rules for chained builder->add

This commit is contained in:
Julien Fastré 2023-06-16 15:26:49 +02:00
parent 3fb97c3945
commit c52ba06ea0
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
2 changed files with 188 additions and 36 deletions

View File

@ -183,43 +183,11 @@ PHP */
if ($stmt instanceof Node\Stmt\Expression
// it must be a method call
&& $stmt->expr instanceof Node\Expr\MethodCall
// the method call must be "add"
&& $stmt->expr->name instanceof Node\Identifier
&& $stmt->expr->name->name === 'add'
// and the method call must apply on the builder (compare with builderName)
&& $stmt->expr->var instanceof Node\Expr\Variable
&& $stmt->expr->var->name === $builderName
// it must have a first argument, a string
// TODO what happens if a value, or a const ?
&& ($stmt->expr->args[0] ?? null) instanceof Node\Arg
&& $stmt->expr->args[0]->value instanceof Node\Scalar\String_
// and a third argument, an array
&& ($stmt->expr->args[2] ?? null) instanceof Node\Arg
&& $stmt->expr->args[2]->value instanceof Node\Expr\Array_
&& false !== ($results = $this->handleMethodCallBuilderAdd($stmt->expr, $builderName))
) {
// we parse on the 3rd argument, to find if there is an 'empty_data' key
$emptyDataIndex = null;
foreach ($stmt->expr->args[2]->value->items as $arrayItemIndex => $item) {
/* @phpstan-ignore-next-line */
if ($item->key->value === 'data') {
$k = $stmt->expr->args[0]->value->value;
$emptyDataToReplace[$k] = $item->value;
$emptyDataIndex = $arrayItemIndex;
}
}
if (null !== $emptyDataIndex) {
$stmt->expr->args[2]->value->items = array_values(
array_filter(
$stmt->expr->args[2]->value->items,
/* @phpstan-ignore-next-line */
fn (Node\Expr\ArrayItem $item) => $item->key->value !== 'data'
)
);
}
$newStmts[] = $stmt;
['stmt' => $newMethodCAll, 'emptyDataToReplace' => $newEmptyDataToReplace] = $results;
$newStmts[] = new Node\Stmt\Expression($newMethodCAll);
$emptyDataToReplace = [...$emptyDataToReplace, ...$newEmptyDataToReplace];
} else {
$newStmts[] = $stmt;
}
@ -229,4 +197,77 @@ PHP */
return ['build_form_method' => $buildFormMethod, "empty_to_replace" => $emptyDataToReplace];
}
private function handleMethodCallBuilderAdd(Node\Expr\MethodCall $methodCall, string $builderName): array|false
{
$emptyDataToReplace = [];
// check for chained method call
if (
// this means that the MethodCall apply on another method call: a chained
$methodCall->var instanceof Node\Expr\MethodCall
) {
// as this is chained, we make a recursion on this method
$resultFormDeepMethodCall = $this->handleMethodCallBuilderAdd($methodCall->var, $builderName);
if (false === $resultFormDeepMethodCall) {
return false;
}
['stmt' => $chainedMethodCall, 'emptyDataToReplace' => $newEmptyDataToReplace] = $resultFormDeepMethodCall;
$emptyDataToReplace = $newEmptyDataToReplace;
$methodCall->var = $chainedMethodCall;
}
if (
$methodCall->var instanceof Node\Expr\Variable
) {
if ($methodCall->var->name !== $builderName) {
// ho, this does not apply on a builder, so we cancel all the method calls
return false;
}
}
if ($methodCall->name instanceof Node\Identifier && $methodCall->name->name !== 'add') {
return ['stmt' => $methodCall, 'emptyDataToReplace' => $emptyDataToReplace];
}
if (
// the method call must be "add"
$methodCall->name instanceof Node\Identifier
&& $methodCall->name->name === 'add'
// it must have a first argument, a string
// TODO what happens if a value, or a const ?
&& ($methodCall->args[0] ?? null) instanceof Node\Arg
&& $methodCall->args[0]->value instanceof Node\Scalar\String_
// and a third argument, an array
&& ($methodCall->args[2] ?? null) instanceof Node\Arg
&& $methodCall->args[2]->value instanceof Node\Expr\Array_
) {
// we parse on the 3rd argument, to find if there is an 'empty_data' key
$emptyDataIndex = null;
foreach ($methodCall->args[2]->value->items as $arrayItemIndex => $item) {
/* @phpstan-ignore-next-line */
if ($item->key->value === 'data' or $item->key->value === 'empty_data') {
$k = $methodCall->args[0]->value->value;
$emptyDataToReplace[$k] = $item->value;
$emptyDataIndex = $arrayItemIndex;
}
}
if (null !== $emptyDataIndex) {
$methodCall->args[2]->value->items = array_values(
array_filter(
$methodCall->args[2]->value->items,
/* @phpstan-ignore-next-line */
fn (Node\Expr\ArrayItem $item) => $item->key->value !== 'data'
)
);
}
return ['stmt' => $methodCall, 'emptyDataToReplace' => $emptyDataToReplace];
}
throw new \RuntimeException("Not supported situation");
}
}

View File

@ -0,0 +1,111 @@
<?php
namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class MyClass implements FilterInterface
{
public function describeAction($data, $format = 'string')
{
// TODO: Implement describeAction() method.
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('foo', PickRollingDateType::class, [
'label' => 'Test thing',
'data' => new RollingDate(RollingDate::T_TODAY)
])
->anotherCall('test')
->add('baz', TextType::class, [
'label' => 'OrNiCar',
'data' => 'Castor'
])
->baz('foo');
}
public function getTitle()
{
// TODO: Implement getTitle() method.
}
public function addRole(): ?string
{
// TODO: Implement addRole() method.
}
public function alterQuery(QueryBuilder $qb, $data)
{
// TODO: Implement alterQuery() method.
}
public function applyOn()
{
// TODO: Implement applyOn() method.
}
}
?>
-----
<?php
namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class MyClass implements FilterInterface
{
public function describeAction($data, $format = 'string')
{
// TODO: Implement describeAction() method.
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('foo', PickRollingDateType::class, [
'label' => 'Test thing'
])
->anotherCall('test')
->add('baz', TextType::class, [
'label' => 'OrNiCar'
])
->baz('foo');
}
public function getFormDefaultData(): array
{
return ['foo' => new RollingDate(RollingDate::T_TODAY), 'baz' => 'Castor'];
}
public function getTitle()
{
// TODO: Implement getTitle() method.
}
public function addRole(): ?string
{
// TODO: Implement addRole() method.
}
public function alterQuery(QueryBuilder $qb, $data)
{
// TODO: Implement alterQuery() method.
}
public function applyOn()
{
// TODO: Implement applyOn() method.
}
}
?>