Skip to content

Commit 2960c46

Browse files
authored
Adds the EloquentOrderByToLatestOrOldestRector rule (#142)
1 parent 845e8a0 commit 2960c46

File tree

7 files changed

+223
-0
lines changed

7 files changed

+223
-0
lines changed

docs/rector_rules_overview.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,29 @@ The EloquentMagicMethodToQueryBuilderRule is designed to automatically transform
478478
- class: [`RectorLaravel\Rector\StaticCall\EloquentMagicMethodToQueryBuilderRector`](../src/Rector/StaticCall/EloquentMagicMethodToQueryBuilderRector.php)
479479

480480
```diff
481+
-User::find(1);
482+
-User::where('email', '[email protected]')->first();
483+
+User::query()->find(1);
484+
+User::query()->where('email', '[email protected]')->first();
485+
486+
<br>
487+
488+
## EloquentOrderByToLatestOrOldestRector
489+
490+
Changes `orderBy()` to `latest()` or `oldest()`
491+
492+
- class: [`RectorLaravel\Rector\MethodCall\EloquentOrderByToLatestOrOldestRector`](../src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php)
493+
494+
```diff
495+
use Illuminate\Database\Eloquent\Builder;
496+
497+
-$builder->orderBy('created_at');
498+
-$builder->orderBy('created_at', 'desc');
499+
-$builder->orderBy('deleted_at');
500+
+$builder->latest();
501+
+$builder->oldest();
502+
+$builder->latest('deleted_at');
503+
481504
use App\Models\User;
482505

483506
-$user = User::find(1);
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace RectorLaravel\Rector\MethodCall;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr\MethodCall;
9+
use PHPStan\Type\ObjectType;
10+
use Rector\Core\Rector\AbstractRector;
11+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
12+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
13+
14+
/**
15+
* @see \RectorLaravel\Tests\Rector\MethodCall\EloquentOrderByToLatestOrOldestRector\EloquentOrderByToLatestOrOldestRectorTest
16+
*/
17+
class EloquentOrderByToLatestOrOldestRector extends AbstractRector
18+
{
19+
public function getRuleDefinition(): RuleDefinition
20+
{
21+
return new RuleDefinition(
22+
'Changes orderBy() to latest() or oldest()',
23+
[
24+
new CodeSample(
25+
<<<'CODE_SAMPLE'
26+
use Illuminate\Database\Eloquent\Builder;
27+
28+
$builder->orderBy('created_at');
29+
$builder->orderBy('created_at', 'desc');
30+
$builder->orderBy('deleted_at');
31+
CODE_SAMPLE
32+
,
33+
<<<'CODE_SAMPLE'
34+
use Illuminate\Database\Eloquent\Builder;
35+
36+
$builder->latest();
37+
$builder->oldest();
38+
$builder->latest('deleted_at');
39+
CODE_SAMPLE
40+
,
41+
),
42+
]
43+
);
44+
}
45+
46+
public function getNodeTypes(): array
47+
{
48+
return [MethodCall::class];
49+
}
50+
51+
public function refactor(Node $node): ?Node
52+
{
53+
if (! $node instanceof MethodCall) {
54+
return null;
55+
}
56+
57+
if ($this->isOrderByMethodCall($node)) {
58+
return $this->convertOrderByToLatest($node);
59+
}
60+
61+
return null;
62+
}
63+
64+
private function isOrderByMethodCall(MethodCall $methodCall): bool
65+
{
66+
// Check if it's a method call to `orderBy`
67+
68+
return $this->isObjectType($methodCall->var, new ObjectType('Illuminate\Database\Query\Builder'))
69+
&& $methodCall->name instanceof Node\Identifier
70+
&& ($methodCall->name->name === 'orderBy' || $methodCall->name->name === 'orderByDesc')
71+
&& count($methodCall->args) > 0;
72+
}
73+
74+
private function convertOrderByToLatest(MethodCall $methodCall): MethodCall
75+
{
76+
if (! isset($methodCall->args[0]) && ! $methodCall->args[0] instanceof Node\VariadicPlaceholder) {
77+
return $methodCall;
78+
}
79+
80+
$columnVar = $methodCall->args[0]->value ?? null;
81+
if ($columnVar === null) {
82+
return $methodCall;
83+
}
84+
85+
$direction = $methodCall->args[1]->value->value ?? 'asc';
86+
if ($this->isName($methodCall->name, 'orderByDesc')) {
87+
$newMethod = 'oldest';
88+
} else {
89+
$newMethod = $direction === 'asc' ? 'latest' : 'oldest';
90+
}
91+
if ($columnVar instanceof Node\Scalar\String_ && $columnVar->value === 'created_at') {
92+
$methodCall->name = new Node\Identifier($newMethod);
93+
$methodCall->args = [];
94+
95+
return $methodCall;
96+
}
97+
98+
if ($columnVar instanceof Node\Scalar\String_) {
99+
$methodCall->name = new Node\Identifier($newMethod);
100+
$methodCall->args = [new Node\Arg(new Node\Scalar\String_($columnVar->value))];
101+
102+
return $methodCall;
103+
}
104+
105+
$methodCall->name = new Node\Identifier($newMethod);
106+
$methodCall->args = [new Node\Arg($columnVar)];
107+
108+
return $methodCall;
109+
}
110+
}
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\EloquentOrderByToLatestOrOldestRector;
6+
7+
use Iterator;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
10+
11+
final class EloquentOrderByToLatestOrOldestRectorTest 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: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\Cast\DatabaseExpressionCastsToMethodCall\Fixture;
4+
5+
use Illuminate\Database\Query\Builder;
6+
7+
$column = 'tested_at';
8+
9+
/** @var Builder $query */
10+
$query->orderBy('created_at');
11+
$query->orderBy('created_at', 'desc');
12+
$query->orderBy('submitted_at');
13+
$query->orderByDesc('submitted_at');
14+
$query->orderBy($column);
15+
16+
?>
17+
-----
18+
<?php
19+
20+
namespace RectorLaravel\Tests\Rector\Cast\DatabaseExpressionCastsToMethodCall\Fixture;
21+
22+
use Illuminate\Database\Query\Builder;
23+
24+
$column = 'tested_at';
25+
26+
/** @var Builder $query */
27+
$query->latest();
28+
$query->oldest();
29+
$query->latest('submitted_at');
30+
$query->oldest('submitted_at');
31+
$query->latest($column);
32+
33+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\Cast\DatabaseExpressionCastsToMethodCall\Fixture;
4+
5+
$query->orderBy('created_at');
6+
7+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace RectorLaravel\Tests\Rector\Cast\DatabaseExpressionCastsToMethodCall\Fixture;
4+
5+
use Illuminate\Database\Query\Builder;
6+
7+
/** @var Builder $query */
8+
$query->orderBy();
9+
10+
?>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use RectorLaravel\Rector\MethodCall\EloquentOrderByToLatestOrOldestRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->import(__DIR__ . '/../../../../../config/config.php');
10+
11+
$rectorConfig->rule(EloquentOrderByToLatestOrOldestRector::class);
12+
};

0 commit comments

Comments
 (0)