diff --git a/.gitignore b/.gitignore index 33c8de0..e05a04a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ composer.phar # PHPUnit .phpunit.result.cache +.phpunit.cache/ # PHP CS Fixer .php-cs-fixer.cache diff --git a/README.md b/README.md index e25e91e..ebf1fcd 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,23 @@ -# rector-template +# rector-laravel-database-expressions -[![packagist](https://img.shields.io/packagist/v/remarkablemark/rector-template)](https://packagist.org/packages/remarkablemark/rector-template) -[![test](https://github.com/remarkablemark/rector-template/actions/workflows/test.yml/badge.svg)](https://github.com/remarkablemark/rector-template/actions/workflows/test.yml) +[![packagist](https://img.shields.io/packagist/v/remarkablemark/rector-laravel-database-expressions)](https://packagist.org/packages/remarkablemark/rector-laravel-database-expressions) +[![test](https://github.com/remarkablemark/rector-laravel-database-expressions/actions/workflows/test.yml/badge.svg)](https://github.com/remarkablemark/rector-laravel-database-expressions/actions/workflows/test.yml) -[Rector](https://github.com/rectorphp/rector) template +[Rector](https://github.com/rectorphp/rector) to refactor Laravel 10 database expressions. + +From [Laravel 10](https://laravel.com/docs/10.x/upgrade#database-expressions): + +> Database "expressions" (typically generated via `DB::raw`) have been rewritten in Laravel 10.x to offer additional functionality in the future. Notably, the grammar's raw string value must now be retrieved via the expression's `getValue(Grammar $grammar)` method. Casting an expression to a string using `(string)` is no longer supported. +> +> If your application is manually casting database expressions to strings using `(string)` or invoking the `__toString` method on the expression directly, you should update your code to invoke the `getValue` method instead: +> +> ```php +> use Illuminate\Support\Facades\DB; +> +> $expression = DB::raw('select 1'); +> +> $string = $expression->getValue(DB::connection()->getQueryGrammar()); +> ``` ## Requirements @@ -14,7 +28,7 @@ PHP >=7.2 Install with [Composer](http://getcomposer.org/): ```sh -composer require --dev rector/rector remarkablemark/rector-template +composer require --dev rector/rector remarkablemark/rector-laravel-database-expressions ``` ## Usage @@ -27,10 +41,10 @@ Register rule in `rector.php`: declare(strict_types=1); use Rector\Config\RectorConfig; -use Remarkablemark\RectorTemplate\ExampleRector; +use Remarkablemark\RectorLaravelDatabaseExpressions\LaravelDatabaseExpressionsRector; return static function (RectorConfig $rectorConfig): void { - $rectorConfig->rule(ExampleRector::class); + $rectorConfig->rule(LaravelDatabaseExpressionsRector::class); }; ``` @@ -52,6 +66,20 @@ Clear the cache and apply the rule: vendor/bin/rector process --clear-cache ``` +## Rule + +### Before + +```php +DB::select(DB::raw('select 1')); +``` + +### After + +```php +DB::select(DB::raw('select 1')->getValue(DB::getQueryGrammar())); +``` + ## License [MIT](LICENSE) diff --git a/composer.json b/composer.json index dda2b11..9ded6da 100644 --- a/composer.json +++ b/composer.json @@ -1,10 +1,13 @@ { - "name": "remarkablemark/rector-template", + "name": "remarkablemark/rector-laravel-database-expressions", "type": "library", - "description": "Rector template", + "description": "Rector to fix Laravel 10 database expressions", "keywords": [ "rector", - "template" + "refactor", + "laravel", + "database", + "expressions" ], "license": "MIT", "authors": [ @@ -20,7 +23,7 @@ }, "autoload": { "psr-4": { - "Remarkablemark\\RectorTemplate\\": "src" + "Remarkablemark\\RectorLaravelDatabaseExpressions\\": "src" } }, "require-dev": { diff --git a/src/ExampleRector.php b/src/ExampleRector.php deleted file mode 100644 index d8290fb..0000000 --- a/src/ExampleRector.php +++ /dev/null @@ -1,55 +0,0 @@ -> - */ - public function getNodeTypes(): array - { - return [MethodCall::class]; - } - - /** - * @param MethodCall $node - */ - public function refactor(Node $node): ?Node - { - $methodName = $this->getName($node->name); - - if (! str_starts_with((string) $methodName, 'set')) { - return null; - } - - $newMethodName = preg_replace('#^set#', 'change', $methodName); - $node->name = new Identifier($newMethodName); - - return $node; - } - - public function getRuleDefinition(): RuleDefinition - { - return new RuleDefinition( - 'Change method calls from set* to change*.', [ - new CodeSample( - // code before - '$user->setPassword("123456");', - // code after - '$user->changePassword("123456");' - ), - ] - ); - } -} diff --git a/src/LaravelDatabaseExpressionsRector.php b/src/LaravelDatabaseExpressionsRector.php new file mode 100644 index 0000000..36d57b7 --- /dev/null +++ b/src/LaravelDatabaseExpressionsRector.php @@ -0,0 +1,71 @@ +getValue(DB::getQueryGrammar()));" + ) + ] + ); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class, StaticCall::class]; + } + + /** + * @param MethodCall|StaticCall $node + */ + public function refactor(Node $node): ?Node + { + /** @var Node */ + $subNode = $node->args[0]->value ?? null; + + if ( + ! isset($subNode->class) || + $this->getName($node->name) !== 'select' || + strpos($this->getName($subNode->class), 'DB') === false || + $this->getName($subNode->name) !== 'raw' + ) { + return null; + } + + $arguments[] = new Arg( + new StaticCall( + new Name('DB'), + 'getQueryGrammar' + ) + ); + + $node->args[0]->value = new MethodCall( + $subNode, + new Identifier('getValue'), + $arguments + ); + + return $node; + } +} diff --git a/tests/config/rector-config.php b/tests/config/rector-config.php index 84a13e8..285b915 100644 --- a/tests/config/rector-config.php +++ b/tests/config/rector-config.php @@ -3,8 +3,8 @@ declare(strict_types=1); use Rector\Config\RectorConfig; -use Remarkablemark\RectorTemplate\ExampleRector; +use Remarkablemark\RectorLaravelDatabaseExpressions\LaravelDatabaseExpressionsRector; return static function (RectorConfig $rectorConfig): void { - $rectorConfig->rule(ExampleRector::class); + $rectorConfig->rule(LaravelDatabaseExpressionsRector::class); }; diff --git a/tests/fixture/SkipRuleTestFixture.php.inc b/tests/fixture/SkipRuleTestFixture.php.inc index 16212c0..31871f5 100644 --- a/tests/fixture/SkipRuleTestFixture.php.inc +++ b/tests/fixture/SkipRuleTestFixture.php.inc @@ -1,13 +1,20 @@ isCorrectPassword($password); - } -} +declare(strict_types=1); + +use Illuminate\Support\Facades\DB; + +DB::select('select 1'); + +DB::select( + 'select 2' +); -?> +$db->select('select 3'); + +DB::raw('select 4'); + +function select5() { + DB::select('select 5'); + $db->select('select 5'); +} diff --git a/tests/fixture/TestFixture.php.inc b/tests/fixture/TestFixture.php.inc index 0bc84b3..855ad3c 100644 --- a/tests/fixture/TestFixture.php.inc +++ b/tests/fixture/TestFixture.php.inc @@ -1,27 +1,27 @@ setPassword($password); - } -} +use Illuminate\Support\Facades\DB; -?> +DB::select(DB::raw('select 1')); + +DB::select( + DB::raw('select 2') +); + +$db->select(DB::raw('select 3')); ----- getValue(DB::getQueryGrammar())); -class SomeClass -{ - public function handlePasswordChange(User $user, string $password) - { - $user->changePassword($password); - } -} +DB::select( + DB::raw('select 2')->getValue(DB::getQueryGrammar()) +); -?> +$db->select(DB::raw('select 3')->getValue(DB::getQueryGrammar()));