From 77888c711bb126dc6c3ed2f8402fbbb88f856a13 Mon Sep 17 00:00:00 2001 From: John Bacon Date: Thu, 12 Oct 2023 11:35:36 -0400 Subject: [PATCH 1/4] Allow configuring EloquentOrderByToLatestOrOldestRector --- docs/rector_rules_overview.md | 33 +++++++++- .../EloquentOrderByToLatestOrOldestRector.php | 61 ++++++++++++++++++- .../Fixture/fixture.php.inc | 8 ++- .../config/configured_rule.php | 12 +++- 4 files changed, 107 insertions(+), 7 deletions(-) diff --git a/docs/rector_rules_overview.md b/docs/rector_rules_overview.md index 89f75625..89affdda 100644 --- a/docs/rector_rules_overview.md +++ b/docs/rector_rules_overview.md @@ -490,17 +490,46 @@ The EloquentMagicMethodToQueryBuilderRule is designed to automatically transform Changes `orderBy()` to `latest()` or `oldest()` +:wrench: **configure it!** + - class: [`RectorLaravel\Rector\MethodCall\EloquentOrderByToLatestOrOldestRector`](../src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php) +```php +ruleWithConfiguration(EloquentOrderByToLatestOrOldestRector::class, [ + EloquentOrderByToLatestOrOldestRector::ALLOWED_PATTERNS => [ + 'created_at', + 'date*', + '*datetime*', + '$renameable_variable_name', + ], + ]); +}; +``` + +↓ + ```diff use Illuminate\Database\Eloquent\Builder; -$builder->orderBy('created_at'); -$builder->orderBy('created_at', 'desc'); --$builder->orderBy('deleted_at'); +-$builder->orderBy('date_created', 'desc'); +-$builder->orderBy('created_datetime', 'asc'); +-$builder->orderBy($renameable_variable_name, 'desc'); +$builder->oldest(); +$builder->latest(); -+$builder->oldest('deleted_at'); ++$builder->latest('date_created'); ++$builder->oldest('created_datetime'); ++$builder->latest($renameable_variable_name); +$builder->orderBy('deleted_at'); ```
diff --git a/src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php b/src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php index 7767a6c6..8bab7a2d 100644 --- a/src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php +++ b/src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php @@ -7,15 +7,27 @@ use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; use PHPStan\Type\ObjectType; +use Rector\Core\Contract\Rector\ConfigurableRectorInterface; use Rector\Core\Rector\AbstractRector; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; +use Webmozart\Assert\Assert; /** * @see \RectorLaravel\Tests\Rector\MethodCall\EloquentOrderByToLatestOrOldestRector\EloquentOrderByToLatestOrOldestRectorTest */ -class EloquentOrderByToLatestOrOldestRector extends AbstractRector +class EloquentOrderByToLatestOrOldestRector extends AbstractRector implements ConfigurableRectorInterface { + /** + * @var string + */ + final public const ALLOWED_PATTERNS = 'allowed_patterns'; + + /** + * @var string[] + */ + private array $allowedPatterns = []; + public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( @@ -54,7 +66,7 @@ public function refactor(Node $node): ?Node return null; } - if ($this->isOrderByMethodCall($node)) { + if ($this->isOrderByMethodCall($node) && $this->isAllowedPattern($node)) { return $this->convertOrderByToLatest($node); } @@ -71,6 +83,39 @@ private function isOrderByMethodCall(MethodCall $methodCall): bool && count($methodCall->args) > 0; } + private function isAllowedPattern(MethodCall $methodCall): bool + { + $columnArg = $methodCall->args[0]->value ?? null; + + // If no patterns are specified, consider all column names as matching + if ($this->allowedPatterns === []) { + return true; + } + + if ($columnArg instanceof Node\Scalar\String_) { + $columnName = $columnArg->value; + + // If specified, only allow certain patterns + foreach ($this->allowedPatterns as $pattern) { + if (fnmatch($pattern, $columnName)) { + return true; + } + } + } + + if ($columnArg instanceof Node\Expr\Variable && is_string($columnArg->name)) { + // Check against allowed patterns + foreach ($this->allowedPatterns as $pattern) { + if (fnmatch(ltrim($pattern, '$'), $columnArg->name)) { + return true; + } + } + } + + + return false; + } + private function convertOrderByToLatest(MethodCall $methodCall): MethodCall { if (! isset($methodCall->args[0]) && ! $methodCall->args[0] instanceof Node\VariadicPlaceholder) { @@ -107,4 +152,16 @@ private function convertOrderByToLatest(MethodCall $methodCall): MethodCall return $methodCall; } + + + /** + * @param mixed[] $configuration + */ + public function configure(array $configuration): void + { + $allowedPatterns = $configuration[self::ALLOWED_PATTERNS] ?? []; + Assert::isArray($allowedPatterns); + + $this->allowedPatterns = $allowedPatterns; + } } diff --git a/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/fixture.php.inc b/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/fixture.php.inc index 8c3e3f64..d41e799a 100644 --- a/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/fixture.php.inc +++ b/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/fixture.php.inc @@ -11,7 +11,9 @@ $query->orderBy('created_at'); $query->orderBy('created_at', 'desc'); $query->orderBy('submitted_at'); $query->orderByDesc('submitted_at'); -$query->orderBy($column); +$query->orderBy($renameable_variable_name); +$query->orderBy($unrenameable_variable_name); +$query->orderBy('disallowed_column_name'); ?> ----- @@ -28,6 +30,8 @@ $query->oldest(); $query->latest(); $query->oldest('submitted_at'); $query->latest('submitted_at'); -$query->oldest($column); +$query->oldest($renameable_variable_name); +$query->orderBy($unrenameable_variable_name); +$query->orderBy('disallowed_column_name'); ?> diff --git a/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/config/configured_rule.php b/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/config/configured_rule.php index d3e399ff..0ddfadfd 100644 --- a/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/config/configured_rule.php +++ b/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/config/configured_rule.php @@ -8,5 +8,15 @@ return static function (RectorConfig $rectorConfig): void { $rectorConfig->import(__DIR__ . '/../../../../../config/config.php'); - $rectorConfig->rule(EloquentOrderByToLatestOrOldestRector::class); + $rectorConfig->ruleWithConfiguration( + EloquentOrderByToLatestOrOldestRector::class, + [ + EloquentOrderByToLatestOrOldestRector::ALLOWED_PATTERNS => [ + 'created_at', + 'submitted_a*', + '*tested_at', + '$renameable_variable_name', + ], + ], + ); }; From 2ae2ef1c445ed6c4259c4751e883ac64435ab40f Mon Sep 17 00:00:00 2001 From: John Bacon Date: Fri, 13 Oct 2023 09:20:48 -0400 Subject: [PATCH 2/4] Update Rector rules overview markdown - Refined the example and some naming - Ran `composer docs` --- docs/rector_rules_overview.md | 26 +++++++++-------- .../EloquentOrderByToLatestOrOldestRector.php | 29 ++++++++++++++----- .../Fixture/fixture.php.inc | 12 ++++---- .../config/configured_rule.php | 2 +- 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/docs/rector_rules_overview.md b/docs/rector_rules_overview.md index 89affdda..dfaa9509 100644 --- a/docs/rector_rules_overview.md +++ b/docs/rector_rules_overview.md @@ -499,16 +499,15 @@ Changes `orderBy()` to `latest()` or `oldest()` declare(strict_types=1); -use RectorLaravel\Rector\PropertyFetch\EloquentOrderByToLatestOrOldestRector; +use RectorLaravel\Rector\MethodCall\EloquentOrderByToLatestOrOldestRector; use Rector\Config\RectorConfig; return static function (RectorConfig $rectorConfig): void { $rectorConfig->ruleWithConfiguration(EloquentOrderByToLatestOrOldestRector::class, [ EloquentOrderByToLatestOrOldestRector::ALLOWED_PATTERNS => [ - 'created_at', - 'date*', - '*datetime*', - '$renameable_variable_name', + 'submitted_a*', + '*tested_at', + '$allowed_variable_name', ], ]); }; @@ -519,17 +518,20 @@ return static function (RectorConfig $rectorConfig): void { ```diff use Illuminate\Database\Eloquent\Builder; + $column = 'tested_at'; + -$builder->orderBy('created_at'); -$builder->orderBy('created_at', 'desc'); --$builder->orderBy('date_created', 'desc'); --$builder->orderBy('created_datetime', 'asc'); --$builder->orderBy($renameable_variable_name, 'desc'); +-$builder->orderBy('submitted_at'); +-$builder->orderByDesc('submitted_at'); +-$builder->orderBy($allowed_variable_name); +$builder->oldest(); +$builder->latest(); -+$builder->latest('date_created'); -+$builder->oldest('created_datetime'); -+$builder->latest($renameable_variable_name); -$builder->orderBy('deleted_at'); ++$builder->oldest('submitted_at'); ++$builder->latest('submitted_at'); ++$builder->oldest($allowed_variable_name); + $builder->orderBy($unallowed_variable_name); + $builder->orderBy('unallowed_column_name'); ```
diff --git a/src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php b/src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php index 8bab7a2d..d65d1464 100644 --- a/src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php +++ b/src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php @@ -10,6 +10,7 @@ use Rector\Core\Contract\Rector\ConfigurableRectorInterface; use Rector\Core\Rector\AbstractRector; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; +use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; use Webmozart\Assert\Assert; @@ -33,24 +34,36 @@ public function getRuleDefinition(): RuleDefinition return new RuleDefinition( 'Changes orderBy() to latest() or oldest()', [ - new CodeSample( - <<<'CODE_SAMPLE' + new ConfiguredCodeSample(<<<'CODE_SAMPLE' use Illuminate\Database\Eloquent\Builder; +$column = 'tested_at'; + $builder->orderBy('created_at'); $builder->orderBy('created_at', 'desc'); -$builder->orderBy('deleted_at'); +$builder->orderBy('submitted_at'); +$builder->orderByDesc('submitted_at'); +$builder->orderBy($allowed_variable_name); +$builder->orderBy($unallowed_variable_name); +$builder->orderBy('unallowed_column_name'); CODE_SAMPLE - , - <<<'CODE_SAMPLE' +,<<<'CODE_SAMPLE' use Illuminate\Database\Eloquent\Builder; +$column = 'tested_at'; + $builder->oldest(); $builder->latest(); -$builder->oldest('deleted_at'); +$builder->oldest('submitted_at'); +$builder->latest('submitted_at'); +$builder->oldest($allowed_variable_name); +$builder->orderBy($unallowed_variable_name); +$builder->orderBy('unallowed_column_name'); CODE_SAMPLE - , - ), +, [self::ALLOWED_PATTERNS => [ + 'submitted_a*', + '*tested_at', + '$allowed_variable_name',]]), ] ); } diff --git a/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/fixture.php.inc b/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/fixture.php.inc index d41e799a..caf83b7d 100644 --- a/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/fixture.php.inc +++ b/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/fixture.php.inc @@ -11,9 +11,9 @@ $query->orderBy('created_at'); $query->orderBy('created_at', 'desc'); $query->orderBy('submitted_at'); $query->orderByDesc('submitted_at'); -$query->orderBy($renameable_variable_name); -$query->orderBy($unrenameable_variable_name); -$query->orderBy('disallowed_column_name'); +$query->orderBy($allowed_variable_name); +$query->orderBy($unallowed_variable_name); +$query->orderBy('unallowed_column_name'); ?> ----- @@ -30,8 +30,8 @@ $query->oldest(); $query->latest(); $query->oldest('submitted_at'); $query->latest('submitted_at'); -$query->oldest($renameable_variable_name); -$query->orderBy($unrenameable_variable_name); -$query->orderBy('disallowed_column_name'); +$query->oldest($allowed_variable_name); +$query->orderBy($unallowed_variable_name); +$query->orderBy('unallowed_column_name'); ?> diff --git a/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/config/configured_rule.php b/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/config/configured_rule.php index 0ddfadfd..c6acbd4d 100644 --- a/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/config/configured_rule.php +++ b/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/config/configured_rule.php @@ -15,7 +15,7 @@ 'created_at', 'submitted_a*', '*tested_at', - '$renameable_variable_name', + '$allowed_variable_name', ], ], ); From 67c53ce0f4881254b609c24f41765f6905bde571 Mon Sep 17 00:00:00 2001 From: John Bacon Date: Mon, 16 Oct 2023 10:10:37 -0400 Subject: [PATCH 3/4] Ensure all `ALLOWED_PATTERNS` values are strings Addresses https://github.com/driftingly/rector-laravel/pull/148#discussion_r1360471558 --- src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php b/src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php index d65d1464..e2372761 100644 --- a/src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php +++ b/src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php @@ -174,6 +174,7 @@ public function configure(array $configuration): void { $allowedPatterns = $configuration[self::ALLOWED_PATTERNS] ?? []; Assert::isArray($allowedPatterns); + Assert::allString($allowedPatterns); $this->allowedPatterns = $allowedPatterns; } From 2a32580997ad8929b915e396635fddf29ac0e4e6 Mon Sep 17 00:00:00 2001 From: John Bacon Date: Thu, 7 Dec 2023 11:09:30 -0500 Subject: [PATCH 4/4] Move query statements to `.php.inc` file --- .../Fixture/fixture.php.inc | 4 ---- .../Fixture/skip_if_column_not_allowed.php.inc | 8 ++++++++ 2 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/skip_if_column_not_allowed.php.inc diff --git a/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/fixture.php.inc b/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/fixture.php.inc index caf83b7d..ccf64f2d 100644 --- a/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/fixture.php.inc +++ b/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/fixture.php.inc @@ -12,8 +12,6 @@ $query->orderBy('created_at', 'desc'); $query->orderBy('submitted_at'); $query->orderByDesc('submitted_at'); $query->orderBy($allowed_variable_name); -$query->orderBy($unallowed_variable_name); -$query->orderBy('unallowed_column_name'); ?> ----- @@ -31,7 +29,5 @@ $query->latest(); $query->oldest('submitted_at'); $query->latest('submitted_at'); $query->oldest($allowed_variable_name); -$query->orderBy($unallowed_variable_name); -$query->orderBy('unallowed_column_name'); ?> diff --git a/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/skip_if_column_not_allowed.php.inc b/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/skip_if_column_not_allowed.php.inc new file mode 100644 index 00000000..315bf0dd --- /dev/null +++ b/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/skip_if_column_not_allowed.php.inc @@ -0,0 +1,8 @@ +orderBy($unallowed_variable_name); +$query->orderBy('unallowed_column_name'); + +?> \ No newline at end of file