Skip to content

Commit 1da0aa0

Browse files
authored
[ORM-300] add missing set, run fixer and add CastDoctrineExprToStringRector (#372)
1 parent 8a70e67 commit 1da0aa0

7 files changed

Lines changed: 192 additions & 3 deletions

File tree

config/sets/doctrine-orm-300.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
declare(strict_types=1);
44

55
use Rector\Config\RectorConfig;
6+
use Rector\Doctrine\Orm30\Rector\MethodCall\CastDoctrineExprToStringRector;
67
use Rector\Doctrine\Orm30\Rector\MethodCall\SetParametersArrayToCollectionRector;
78
use Rector\Renaming\Rector\Name\RenameClassRector;
89

910
return static function (RectorConfig $rectorConfig): void {
10-
$rectorConfig->rules([
11-
SetParametersArrayToCollectionRector::class,
12-
]);
11+
$rectorConfig->rules([SetParametersArrayToCollectionRector::class, CastDoctrineExprToStringRector::class]);
1312

1413
$rectorConfig->ruleWithConfiguration(RenameClassRector::class, [
1514
'Doctrine\ORM\ORMException' => 'Doctrine\ORM\Exception\ORMException',
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Doctrine\Tests\Orm30\Rector\MethodCall\CastDoctrineExprToStringRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class CastDoctrineExprToStringRectorTest extends AbstractRectorTestCase
12+
{
13+
#[DataProvider('provideData')]
14+
public function test(string $filePath): void
15+
{
16+
$this->doTestFile($filePath);
17+
}
18+
19+
public static function provideData(): Iterator
20+
{
21+
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
22+
}
23+
24+
public function provideConfigFilePath(): string
25+
{
26+
return __DIR__ . '/configured_rule.php';
27+
}
28+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Rector\Doctrine\Tests\Orm30\Rector\MethodCall\CastDoctrineExprToStringRector\Fixture;
4+
5+
use Doctrine\ORM\Query\Expr;
6+
7+
final class ExprBuilderCall
8+
{
9+
public function createCustomExprBuilder()
10+
{
11+
$expr = new Expr();
12+
$expr->like($expr->lower('test'), $expr->lower($expr->literal('%' . 'query' . '%')));
13+
$expr->like($expr->lower('test')->__toString(), $expr->lower($expr->literal('%' . 'query' . '%'))->__toString());
14+
}
15+
}
16+
?>
17+
-----
18+
<?php
19+
20+
namespace Rector\Doctrine\Tests\Orm30\Rector\MethodCall\CastDoctrineExprToStringRector\Fixture;
21+
22+
use Doctrine\ORM\Query\Expr;
23+
24+
final class ExprBuilderCall
25+
{
26+
public function createCustomExprBuilder()
27+
{
28+
$expr = new Expr();
29+
$expr->like((string) $expr->lower('test'), (string) $expr->lower($expr->literal('%' . 'query' . '%')));
30+
$expr->like($expr->lower('test')->__toString(), $expr->lower($expr->literal('%' . 'query' . '%'))->__toString());
31+
}
32+
}
33+
?>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Rector\Doctrine\Tests\Orm30\Rector\MethodCall\CastDoctrineExprToStringRector\Fixture;
4+
5+
use Doctrine\ORM\Query\Expr;
6+
7+
final class SkipLiteral
8+
{
9+
public function createCustomExprBuilder()
10+
{
11+
$expr = new Expr();
12+
$expr->eq('column', $expr->literal('value'));
13+
}
14+
}
15+
?>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
7+
return RectorConfig::configure()
8+
->withRules([\Rector\Doctrine\Orm30\Rector\MethodCall\CastDoctrineExprToStringRector::class]);
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Doctrine\Orm30\Rector\MethodCall;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Arg;
9+
use PhpParser\Node\Expr\Cast\String_;
10+
use PhpParser\Node\Expr\MethodCall;
11+
use PHPStan\Type\ObjectType;
12+
use Rector\Rector\AbstractRector;
13+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
14+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
15+
16+
/**
17+
* @see https://github.com/doctrine/orm/commit/4d73e3ce7801d3bf3254257332e903d8ecea4096
18+
*/
19+
final class CastDoctrineExprToStringRector extends AbstractRector
20+
{
21+
/**
22+
* @var array<string>
23+
*/
24+
private array $targetMethods = [
25+
'like', 'notLike', 'eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'between',
26+
'in', 'notIn', 'isMemberOf', 'isInstanceOf',
27+
];
28+
29+
/**
30+
* @var array<string>
31+
*/
32+
private array $exprFuncMethods = [
33+
'lower', 'upper', 'length', 'trim', 'avg', 'max', 'min', 'count',
34+
'countDistinct', 'exists', 'all', 'some', 'any', 'not', 'abs', 'sqrt',
35+
];
36+
37+
public function getRuleDefinition(): RuleDefinition
38+
{
39+
return new RuleDefinition(
40+
'Casts Doctrine Expr\x to string where necessary.',
41+
[
42+
new CodeSample(
43+
<<<'CODE_SAMPLE'
44+
$statements->add(
45+
$builder->expr()->like(
46+
$builder->expr()->lower($column),
47+
$builder->expr()->lower($builder->expr()->literal('%'.$like.'%'))
48+
)
49+
);
50+
CODE_SAMPLE
51+
,
52+
<<<'CODE_SAMPLE'
53+
$statements->add(
54+
$builder->expr()->like(
55+
(string) $builder->expr()->lower($column),
56+
(string) $builder->expr()->lower($builder->expr()->literal('%'.$like.'%'))
57+
)
58+
);
59+
CODE_SAMPLE
60+
)]
61+
);
62+
}
63+
64+
public function getNodeTypes(): array
65+
{
66+
return [MethodCall::class];
67+
}
68+
69+
public function refactor(Node $node): ?Node
70+
{
71+
if (! $node instanceof MethodCall) {
72+
return null;
73+
}
74+
75+
if (! $this->isObjectType($node->var, new ObjectType('Doctrine\ORM\Query\Expr'))) {
76+
return null;
77+
}
78+
79+
if (! in_array($this->getName($node->name), $this->targetMethods, true)) {
80+
return null;
81+
}
82+
83+
// Iterate through method arguments and cast `Expr\Func` calls to string
84+
$hasChanged = false;
85+
foreach ($node->args as $arg) {
86+
if (! $arg instanceof Arg) {
87+
return null;
88+
}
89+
if ($arg->value instanceof MethodCall
90+
&& $this->isObjectType($arg->value->var, new ObjectType('Doctrine\ORM\Query\Expr'))
91+
&& in_array($this->getName($arg->value->name), $this->exprFuncMethods, true)
92+
) {
93+
$arg->value = new String_($arg->value);
94+
$hasChanged = true;
95+
}
96+
}
97+
98+
return $hasChanged ? $node : null;
99+
}
100+
}

src/Set/SetProvider/DoctrineSetProvider.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ public function provide(): array
7979
'2.14',
8080
__DIR__ . '/../../../config/sets/doctrine-orm-214.php',
8181
),
82+
new ComposerTriggeredSet(
83+
SetGroup::DOCTRINE,
84+
'doctrine/orm',
85+
'3.0',
86+
__DIR__ . '/../../../config/sets/doctrine-orm-300.php',
87+
),
8288

8389
new Set(SetGroup::ATTRIBUTES, 'Doctrine ORM', __DIR__ . '/../../../config/sets/attributes/doctrine.php'),
8490
new Set(SetGroup::ATTRIBUTES, 'Gedmo', __DIR__ . '/../../../config/sets/attributes/gedmo.php'),

0 commit comments

Comments
 (0)