Skip to content

Commit

Permalink
Add a Rector Rule to change the argument type from array to ArrayColl…
Browse files Browse the repository at this point in the history
…ection for the setParameters ORM QueryBuilder method (#326)
  • Loading branch information
marcelthole authored Feb 28, 2025
1 parent 04998ca commit 75a098f
Show file tree
Hide file tree
Showing 8 changed files with 310 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Rector\Doctrine\Tests\Orm30\Rector\MethodCall\SetParametersArrayToCollectionRector\Fixture;

final class QueryBuilderCall
{
public function createCustomQueryBuilder()
{
$ormQueryBuilder = new \Doctrine\ORM\QueryBuilder();
$ormQueryBuilder->setParameters([
new \Doctrine\ORM\Query\Parameter('foo', 'bar'),
new \Doctrine\ORM\Query\Parameter('bar', 1),
new \Doctrine\ORM\Query\Parameter('baz', false),
]);
}
}
?>
-----
<?php

namespace Rector\Doctrine\Tests\Orm30\Rector\MethodCall\SetParametersArrayToCollectionRector\Fixture;

final class QueryBuilderCall
{
public function createCustomQueryBuilder()
{
$ormQueryBuilder = new \Doctrine\ORM\QueryBuilder();
$ormQueryBuilder->setParameters(new \Doctrine\Common\Collections\ArrayCollection([new \Doctrine\ORM\Query\Parameter('foo', 'bar'), new \Doctrine\ORM\Query\Parameter('bar', 1), new \Doctrine\ORM\Query\Parameter('baz', false)]));
}
}
?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Rector\Doctrine\Tests\Orm30\Rector\MethodCall\SetParametersArrayToCollectionRector\Fixture;

final class QueryBuilderCall
{
public function createCustomQueryBuilder()
{
$ormQueryBuilder = new \Doctrine\ORM\QueryBuilder();
$ormQueryBuilder->setParameters(new \Doctrine\Common\Collections\ArrayCollection([
'foo' => 'bar',
'bar' => 1,
'baz' => false
]));
}
}
?>
-----
<?php

namespace Rector\Doctrine\Tests\Orm30\Rector\MethodCall\SetParametersArrayToCollectionRector\Fixture;

final class QueryBuilderCall
{
public function createCustomQueryBuilder()
{
$ormQueryBuilder = new \Doctrine\ORM\QueryBuilder();
$ormQueryBuilder->setParameters(new \Doctrine\Common\Collections\ArrayCollection([new \Doctrine\ORM\Query\Parameter('foo', 'bar'), new \Doctrine\ORM\Query\Parameter('bar', 1), new \Doctrine\ORM\Query\Parameter('baz', false)]));
}
}
?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Rector\Doctrine\Tests\Orm30\Rector\MethodCall\SetParametersArrayToCollectionRector\Fixture;

final class QueryBuilderCall
{
public function createCustomQueryBuilder()
{
$ormQueryBuilder = new \Doctrine\ORM\QueryBuilder();
$ormQueryBuilder->setParameters([
'foo' => 'bar',
'bar' => 1,
'baz' => false
]);
}
}
?>
-----
<?php

namespace Rector\Doctrine\Tests\Orm30\Rector\MethodCall\SetParametersArrayToCollectionRector\Fixture;

final class QueryBuilderCall
{
public function createCustomQueryBuilder()
{
$ormQueryBuilder = new \Doctrine\ORM\QueryBuilder();
$ormQueryBuilder->setParameters(new \Doctrine\Common\Collections\ArrayCollection([new \Doctrine\ORM\Query\Parameter('foo', 'bar'), new \Doctrine\ORM\Query\Parameter('bar', 1), new \Doctrine\ORM\Query\Parameter('baz', false)]));
}
}
?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Rector\Doctrine\Tests\Orm30\Rector\MethodCall\SetParametersArrayToCollectionRector\Fixture;

final class QueryBuilderCall
{
public function createCustomQueryBuilder()
{
$ormQueryBuilder = new \Doctrine\ORM\QueryBuilder();
$ormQueryBuilder->setParameters(new \Doctrine\Common\Collections\ArrayCollection([
new \Doctrine\ORM\Query\Parameter('foo', 'bar'),
new \Doctrine\ORM\Query\Parameter('bar', 1),
new \Doctrine\ORM\Query\Parameter('baz', false),
]));
}
}
?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Rector\Doctrine\Tests\Orm30\Rector\MethodCall\SetParametersArrayToCollectionRector\Fixture;

final class QueryBuilderCall
{
public function createCustomQueryBuilderWithNumeric()
{
$ormQueryBuilder = new \Doctrine\ORM\QueryBuilder();
$ormQueryBuilder->setParameters([
'one',
'two',
'three'
]);
}
}
?>
-----
<?php

namespace Rector\Doctrine\Tests\Orm30\Rector\MethodCall\SetParametersArrayToCollectionRector\Fixture;

final class QueryBuilderCall
{
public function createCustomQueryBuilderWithNumeric()
{
$ormQueryBuilder = new \Doctrine\ORM\QueryBuilder();
$ormQueryBuilder->setParameters(new \Doctrine\Common\Collections\ArrayCollection([new \Doctrine\ORM\Query\Parameter(0, 'one'), new \Doctrine\ORM\Query\Parameter(1, 'two'), new \Doctrine\ORM\Query\Parameter(2, 'three')]));
}
}
?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Rector\Doctrine\Tests\Orm30\Rector\MethodCall\SetParametersArrayToCollectionRector;

use Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class SetParametersArrayToCollectionRectorTest extends AbstractRectorTestCase
{
#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public static function provideData(): Iterator
{
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/configured_rule.php';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;

return RectorConfig::configure()
->withRules([\Rector\Doctrine\Orm30\Rector\MethodCall\SetParametersArrayToCollectionRector::class]);
133 changes: 133 additions & 0 deletions rules/Orm30/Rector/MethodCall/SetParametersArrayToCollectionRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?php

declare(strict_types=1);

namespace Rector\Doctrine\Orm30\Rector\MethodCall;

use PhpParser\Node;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ArrayItem;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Scalar\LNumber;
use PHPStan\Type\ObjectType;
use Rector\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

/**
* @see https://github.com/doctrine/orm/pull/9490
* @see https://github.com/doctrine/orm/blob/3.0.x/UPGRADE.md#query-querybuilder-and-nativequery-parameters-bc-break
*/
final class SetParametersArrayToCollectionRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Change the argument type for setParameters from array to ArrayCollection and Parameter calls',
[
new CodeSample(
<<<'CODE_SAMPLE'
$entityManager->createQueryBuilder()->setParameters([
'foo' => 'bar'
]);
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
$entityManager->createQueryBuilder()->setParameters(new \Doctrine\Common\Collections\ArrayCollection([
new \Doctrine\ORM\Query\Parameter('foo', 'bar')
]));
CODE_SAMPLE
)]
);
}

/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [MethodCall::class];
}

/**
* @param MethodCall $node
*/
public function refactor(Node $node): Node|null
{
$varType = $this->nodeTypeResolver->getType($node->var);

if (! $varType instanceof ObjectType) {
return null;
}

if (! $varType->isInstanceOf('Doctrine\\ORM\\QueryBuilder')->yes()) {
return null;
}

if ($node->isFirstClassCallable()) {
return null;
}

if (! $this->isNames($node->name, ['setParameters'])) {
return null;
}

$args = $node->getArgs();
if (\count($args) !== 1) {
return null;
}

$currentArg = $args[0]->value;
$isAlreadyAnArrayCollection = false;

$currentArgType = $this->nodeTypeResolver->getType($currentArg);
if (
$currentArgType instanceof ObjectType
&& $currentArgType->isInstanceOf('Doctrine\\Common\\Collections\\ArrayCollection')
->yes()
&& $currentArg instanceof New_
&& count($currentArg->args) === 1
&& $currentArg->args[0] instanceof Arg
) {
$currentArg = $currentArg->args[0]->value;
$isAlreadyAnArrayCollection = true;
}

if (! $currentArg instanceof Array_) {
return null;
}

$changedParameterType = false;
$parameters = [];
foreach ($currentArg->items as $index => $value) {
if (! $value instanceof ArrayItem) {
return null;
}

$arrayValueType = $this->nodeTypeResolver->getType($value->value);
if (! $arrayValueType instanceof ObjectType || ! $arrayValueType->isInstanceOf(
'Doctrine\\ORM\\Query\\Parameter'
)->yes()) {
$newParameter = new New_(new FullyQualified('Doctrine\\ORM\\Query\\Parameter'));
$newParameter->args = [new Arg($value->key ?? new LNumber($index)), new Arg($value->value)];
$value->value = $newParameter;
$changedParameterType = true;
}

$parameters[] = new ArrayItem($value->value);
}

if ($changedParameterType === false && $isAlreadyAnArrayCollection) {
return null;
}

$newCollection = new New_(new FullyQualified('Doctrine\\Common\\Collections\\ArrayCollection'));
$newCollection->args = [new Arg(new Array_($parameters))];

$node->args = [new Arg($newCollection)];
return $node;
}
}

0 comments on commit 75a098f

Please sign in to comment.