Skip to content

Commit 845e8a0

Browse files
authored
Adds the EloquentWhereTypeHintClosureParameterRector (#141)
1 parent 0e1d63c commit 845e8a0

File tree

9 files changed

+269
-0
lines changed

9 files changed

+269
-0
lines changed

docs/rector_rules_overview.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,22 @@ The EloquentMagicMethodToQueryBuilderRule is designed to automatically transform
486486

487487
<br>
488488

489+
## EloquentWhereTypeHintClosureParameterRector
490+
491+
Change typehint of closure parameter in where method of Eloquent Builder
492+
493+
- class: [`RectorLaravel\Rector\MethodCall\EloquentWhereTypeHintClosureParameterRector`](../src/Rector/MethodCall/EloquentWhereTypeHintClosureParameterRector.php)
494+
495+
```diff
496+
-$query->where(function ($query) {
497+
+$query->where(function (\Illuminate\Contracts\Database\Eloquent\Builder $query) {
498+
$query->where('id', 1);
499+
});
500+
```
501+
502+
<br>
503+
504+
489505
## EloquentWhereRelationTypeHintingParameterRector
490506

491507
Add type hinting to where relation has methods e.g. `whereHas`, `orWhereHas`, `whereDoesntHave`, `orWhereDoesntHave`, `whereHasMorph`, `orWhereHasMorph`, `whereDoesntHaveMorph`, `orWhereDoesntHaveMorph`
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace RectorLaravel\Rector\MethodCall;
6+
7+
use PhpParser\Node;
8+
use PHPStan\Type\ObjectType;
9+
use Rector\Core\Rector\AbstractRector;
10+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
11+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
12+
13+
/**
14+
* @see \RectorLaravel\Tests\Rector\MethodCall\EloquentWhereTypeHintClosureParameterRector\EloquentWhereTypeHintClosureParameterRectorTest
15+
*/
16+
class EloquentWhereTypeHintClosureParameterRector extends AbstractRector
17+
{
18+
public function getRuleDefinition(): RuleDefinition
19+
{
20+
return new RuleDefinition(
21+
'Change typehint of closure parameter in where method of Eloquent Builder',
22+
[
23+
new CodeSample(
24+
<<<'CODE_SAMPLE'
25+
$query->where(function ($query) {
26+
$query->where('id', 1);
27+
});
28+
CODE_SAMPLE
29+
,
30+
<<<'CODE_SAMPLE'
31+
$query->where(function (\Illuminate\Contracts\Database\Eloquent\Builder $query) {
32+
$query->where('id', 1);
33+
});
34+
CODE_SAMPLE
35+
,
36+
),
37+
]
38+
);
39+
}
40+
41+
public function getNodeTypes(): array
42+
{
43+
return [Node\Expr\MethodCall::class, Node\Expr\StaticCall::class];
44+
}
45+
46+
public function refactor(Node $node): ?Node
47+
{
48+
if (! $node instanceof Node\Expr\MethodCall && ! $node instanceof Node\Expr\StaticCall) {
49+
return null;
50+
}
51+
52+
if ($this->isWhereMethodWithClosureOrArrowFunction($node)) {
53+
$this->changeClosureParamType($node);
54+
55+
return $node;
56+
}
57+
58+
return null;
59+
}
60+
61+
private function isWhereMethodWithClosureOrArrowFunction(
62+
Node\Expr\MethodCall|Node\Expr\StaticCall $node
63+
): bool {
64+
if (! $this->expectedObjectTypeAndMethodCall($node)) {
65+
return false;
66+
}
67+
68+
if (
69+
! ($node->getArgs()[0]->value ?? null) instanceof Node\Expr\Closure &&
70+
! ($node->getArgs()[0]->value ?? null) instanceof Node\Expr\ArrowFunction
71+
) {
72+
return false;
73+
}
74+
75+
return true;
76+
}
77+
78+
private function changeClosureParamType(Node\Expr\MethodCall|Node\Expr\StaticCall $node): void
79+
{
80+
/** @var Node\Expr\ArrowFunction|Node\Expr\Closure $closure */
81+
$closure = $node->getArgs()[0]
82+
->value;
83+
84+
if (! isset($closure->getParams()[0])) {
85+
return;
86+
}
87+
88+
$param = $closure->getParams()[0];
89+
90+
if ($param->type instanceof Node\Name) {
91+
return;
92+
}
93+
94+
$param->type = new Node\Name\FullyQualified('Illuminate\Contracts\Database\Query\Builder');
95+
}
96+
97+
private function expectedObjectTypeAndMethodCall(Node\Expr\MethodCall|Node\Expr\StaticCall $node): bool
98+
{
99+
return match (true) {
100+
$node instanceof Node\Expr\MethodCall && $this->isObjectType(
101+
$node->var,
102+
new ObjectType('Illuminate\Contracts\Database\Query\Builder')
103+
) => true,
104+
$node instanceof Node\Expr\StaticCall && $this->isObjectType(
105+
$node->class,
106+
new ObjectType('Illuminate\Database\Eloquent\Model')
107+
) => true,
108+
default => false,
109+
} && $this->isNames($node->name, ['where', 'orWhere']);
110+
}
111+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace RectorLaravel\Tests\Rector\MethodCall\EloquentWhereTypeHintClosureParameterRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class EloquentWhereTypeHintClosureParameterRectorTest 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__ . '/config/configured_rule.php';
27+
}
28+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\MethodCall\EloquentWhereTypeHintClosureParameterRector\Fixture;
4+
5+
/** @var \Illuminate\Contracts\Database\Query\Builder $query */
6+
$query->where(function ($query) {
7+
$query->where('id', 1)
8+
->orWhere('id', 2);
9+
});
10+
11+
?>
12+
-----
13+
<?php
14+
15+
namespace RectorLaravel\Tests\Rector\MethodCall\EloquentWhereTypeHintClosureParameterRector\Fixture;
16+
17+
/** @var \Illuminate\Contracts\Database\Query\Builder $query */
18+
$query->where(function (\Illuminate\Contracts\Database\Query\Builder $query) {
19+
$query->where('id', 1)
20+
->orWhere('id', 2);
21+
});
22+
23+
?>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\MethodCall\EloquentWhereTypeHintClosureParameterRector\Fixture;
4+
5+
class User extends \Illuminate\Database\Eloquent\Model
6+
{
7+
8+
}
9+
10+
User::where(function ($query) {
11+
$query->where('id', 1)
12+
->orWhere('id', 2);
13+
});
14+
15+
?>
16+
-----
17+
<?php
18+
19+
namespace RectorLaravel\Tests\Rector\MethodCall\EloquentWhereTypeHintClosureParameterRector\Fixture;
20+
21+
class User extends \Illuminate\Database\Eloquent\Model
22+
{
23+
24+
}
25+
26+
User::where(function (\Illuminate\Contracts\Database\Query\Builder $query) {
27+
$query->where('id', 1)
28+
->orWhere('id', 2);
29+
});
30+
31+
?>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\MethodCall\EloquentWhereTypeHintClosureParameterRector\Fixture;
4+
5+
/** @var \Illuminate\Contracts\Database\Query\Builder $query */
6+
$query->where(fn ($query) =>
7+
$query->where('id', 1)
8+
->orWhere('id', 2)
9+
);
10+
11+
?>
12+
-----
13+
<?php
14+
15+
namespace RectorLaravel\Tests\Rector\MethodCall\EloquentWhereTypeHintClosureParameterRector\Fixture;
16+
17+
/** @var \Illuminate\Contracts\Database\Query\Builder $query */
18+
$query->where(fn (\Illuminate\Contracts\Database\Query\Builder $query) =>
19+
$query->where('id', 1)
20+
->orWhere('id', 2)
21+
);
22+
23+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
/** @var \Illuminate\Contracts\Database\Query\Builder $query */
4+
$query->where('name', 'a');
5+
6+
class User extends \Illuminate\Database\Eloquent\Model
7+
{
8+
9+
}
10+
11+
User::where('name', 'a');
12+
13+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\MethodCall\EloquentWhereTypeHintClosureParameterRector\Fixture;
4+
5+
$obj->whereHas('posts', fn ($query) =>
6+
$query->where('is_published', true)
7+
);
8+
9+
RandomClass::whereHas('posts', fn ($query) =>
10+
$query->where('is_published', true)
11+
);
12+
13+
?>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
7+
return static function (RectorConfig $rectorConfig): void {
8+
$rectorConfig->import(__DIR__ . '/../../../../../config/config.php');
9+
10+
$rectorConfig->rule(\RectorLaravel\Rector\MethodCall\EloquentWhereTypeHintClosureParameterRector::class);
11+
};

0 commit comments

Comments
 (0)