diff --git a/docs/rector_rules_overview.md b/docs/rector_rules_overview.md index 3e686b6e..6acaf11a 100644 --- a/docs/rector_rules_overview.md +++ b/docs/rector_rules_overview.md @@ -490,17 +490,48 @@ 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 => [ + 'submitted_a*', + '*tested_at', + '$allowed_variable_name', + ], + ]); +}; +``` + +↓ + ```diff 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->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'); ```
diff --git a/src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php b/src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php index 7767a6c6..e2372761 100644 --- a/src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php +++ b/src/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector.php @@ -7,38 +7,63 @@ 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\CodeSample\ConfiguredCodeSample; 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( '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',]]), ] ); } @@ -54,7 +79,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 +96,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 +165,17 @@ 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); + Assert::allString($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..ccf64f2d 100644 --- a/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/fixture.php.inc +++ b/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/Fixture/fixture.php.inc @@ -11,7 +11,7 @@ $query->orderBy('created_at'); $query->orderBy('created_at', 'desc'); $query->orderBy('submitted_at'); $query->orderByDesc('submitted_at'); -$query->orderBy($column); +$query->orderBy($allowed_variable_name); ?> ----- @@ -28,6 +28,6 @@ $query->oldest(); $query->latest(); $query->oldest('submitted_at'); $query->latest('submitted_at'); -$query->oldest($column); +$query->oldest($allowed_variable_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 diff --git a/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/config/configured_rule.php b/tests/Rector/MethodCall/EloquentOrderByToLatestOrOldestRector/config/configured_rule.php index d3e399ff..c6acbd4d 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', + '$allowed_variable_name', + ], + ], + ); };