rectorとrector-laravelでLaravelをアップデートしてみる

こんにちは。Fusicの@hkusaba です。
[Fusic Advent Calendar 2023](https://qiita.com/advent-calendar/2023/fusic) の2日目ですね。

今回は、rectorとrector-laravelを使用して、PHP・Laravelをアップデートしたお話をします。
弊社内で行っている開発合宿で触る機会がありました！いい会社です。

## はじめに

### rectorとは何ぞや

公式がこちらです。

https://github.com/rectorphp/rector/tree/main

rectorには2つの特徴があります。 (Google翻訳です。

> 1. 即時アップグレード
Rector は、PHP 5.3 から 8.2 へのアップグレードと、 Symfony、PHPUnit、Doctrineなどの主要なオープンソース プロジェクトをサポートするようになりました。手間をかけずに常に最新の PHP とフレームワークを使用したいですか?
Rector を使用して即時アップグレードを処理します。
> 2. 自動リファクタリング
必要なコード品質はあるものの、チーム内の新しい開発者と一緒にそれを維持するのに苦労していませんか? 上級開発者全員が寝ているときでも、スマートなコードレビューを見たいですか?
Rector を CI に追加すると、コードが継続的にリファクタリングされ、コードの品質が高く保たれます。
自動リファクタリングを設定する方法については、ブログ投稿をお読みください。

PHPやSymfony、PHPunit、さらには、Laravelにも対応しています。

https://github.com/driftingly/rector-laravel

例えば、下記の記述のみで、PHP8.2とLaravel10にアップデートできると思うと、わくわくしませんか？

```php:rector.php
<?php

use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;
use RectorLaravel\Set\LaravelLevelSetList;

return static function (RectorConfig $rectorConfig): void {

    $rectorConfig->paths([
        __DIR__ . '/app',
        __DIR__ . '/config',
        __DIR__ . '/database',
    ]);

    $rectorConfig->sets([
        LevelSetList::UP_TO_PHP_82,
        LaravelLevelSetList::UP_TO_LARAVEL_100,
    ]);
};
```

そこでやってみることにしました！

## やってみよう

はじめ方は、だいたい公式のReadmeと同じです。やっていきましょう。

```bash
# rectorとlaravel-rectorを導入
composer require rector/rector --dev
composer require driftingly/rector-laravel --dev

# rectorの設定ファイルを、vendorディレクトリやcomposer.jsonと同じ階層のディレクトリに生成
# 内容は、公式Readmeのもの、もしくは、記事上部のrector.phpの内容でOKです
vi rector.php

# 設定を書き終えたらdry-run or 実行
# $rectorConfig->paths() で対象を指定していない場合、appなどの対象を指定する
# 指定している場合は書かなくてOK
vendor/bin/rector process app --dry-run
```

## やってみた

`vendor/bin/rector process --dry-run` すると、下記のようになります。

```diff
1) app/fugafuga/hogehoge/HogeHoge.php:8
    ---------- begin diff ----------
@@ @@
 class HogeHoge
 {
     /**
-     * slackService
-     *
-     * @var SlackService $slackService
-     */
-    public SlackService $slackService;
-
-    /**
      * __construct
      *
-     * @param  SlackService $slackService
      * @return void
      */
-    public function __construct(SlackService $slackService)
+    public function __construct(
+        /**
+         * slackService
+         */
+        public SlackService $slackService
+    )
     {
-        $this->slackService = $slackService;
     }
     /**
      * Handle the event.
      *
-     * @param  HogeHoge  $event
      * @return void
      */
     public function handle(HogeHoge $event): void
    ----------- end diff -----------
Applied rules:
 * ClassPropertyAssignToConstructorPromotionRector (https://wiki.php.net/rfc/constructor_promotion https://github.com/php/php-src/pull/5291)
 * MixedTypeRector
```

変更対象になったファイルのdiffと、適用されたルールがズラッと表示されます。
このケースでは、PHP8.0で導入された`Constructor Property Promotion`が適用されました。
コメントが引数に入るのが惜しいですが、これでも全然動きます。
元となるRFCを表示してくれるので、仮にわからないルールの適用でも安心です。

また、`MixedTypeRector`によって、`handle(HogeHoge $event)` は引数の型が明示なので、PHPDocからその旨が消されています。冗長な記述がスッキリしましたね。

#### ルールの詳細

rectorやrector-laravelのルールの詳細は、この辺りから確認できます。
exampleもあって、見やすいですね。

https://github.com/rectorphp/rector/blob/main/docs/rector_rules_overview.md

https://github.com/driftingly/rector-laravel/blob/main/docs/rector_rules_overview.md

---

ほかにも、下記のようなルールが適用されました。

```diff
-            ? array_merge($rules, $this->hogehoge())
+            ? [...$rules, ...$this->hogehoge()]
 ----------- end diff -----------

Applied rules:
 * LongArrayToShortArrayRector
 * ArraySpreadInsteadOfArrayMergeRector (https://wiki.php.net/rfc/spread_operator_for_array)
```

```diff
14) app/Providers/RouteServiceProvider.php:16

    ---------- begin diff ----------
@@ @@
      *
      * @var string
      */
-    public const HOME = '/hoge';
+    final public const HOME = '/hoge';

     /**
      * The controller namespace for the application.
@@ @@
      */
     protected function configureRateLimiting(): void
     {
-        RateLimiter::for('api', function (Request $request) {
-            return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip());
-        });
+        RateLimiter::for('api', fn(Request $request) => Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip()));
     }
 }
    ----------- end diff -----------

Applied rules:
 * ClosureToArrowFunctionRector (https://wiki.php.net/rfc/arrow_functions_v2)
 * FinalizePublicClassConstantRector (https://php.watch/versions/8.1/final-class-const)
```

少し地味な変更ですが、直近のPHPの更新に適応するように、いい感じにリファクタされました。
地味な変更の理由の1つとして、今回はPHP8.0->8.2 とLaravel8.x->10.x と比較的差分が少なかったことも考えられます。
個人的に驚いた点としては、細かい調整を必要とせず、リファクタされた時点で動くことでした。

一方で、コメントのズレは調整してプルリクエスト出したいですし、自身のニーズにマッチしないルールはskipするなど、チューニングの余地があります。

https://getrector.com/documentation/ignoring-rules-or-paths

## アップグレードのルールセットについて

ルールセットの`LevelSetList::UP_TO_PHP_82` が何しているのか気になったので、調べました。

このように、PHP8.2のルールセットと、PHP8.1へのアップデートのルールセットが設定されている状態となっています。`UP_TO_PHP_81` の中も同様に、8.1のルールセットと、8.0へのアップデートのルールセットが設定されています。
そのため、`UP_TO_PHP_82` を設定すれば、下位バージョンのアップデートルールセットも再帰的に設定されるので、最新版のアップデートのルールセットのみを設定する形で大丈夫ですね。

```php:up-to-php82.php
return static function (RectorConfig $rectorConfig) : void {
    $rectorConfig->sets([SetList::PHP_82, LevelSetList::UP_TO_PHP_81]);
    // parameter must be defined after import, to override imported param version
    $rectorConfig->phpVersion(PhpVersion::PHP_82);
};
```

https://github.com/rectorphp/rector/blob/69a95d55f95014cb2ffe6b4b0987e5b337b6635a/config/set/level/up-to-php82.php

rector-laravel も同様です。

https://github.com/driftingly/rector-laravel/blob/main/config/sets/level/up-to-laravel-100.php

## Laravelアップデートについて

rectorとは別件で、Laravelのアップデートに関して、つまりどころがあったので記載します。

ほかのライブラリをアップデートしようとしたときに、 `composer update –with-all-dependencies` が依存関係から通らないという現象です。

これに関しては、8.x->9.xなどバージョンごとに、公式のアップグレードガイドに詳細が書いてあるので、こちらを確認・実行するようにしましょう。

https://laravel.com/docs/9.x/upgrade#updating-dependencies
https://laravel.com/docs/10.x/upgrade#updating-dependencies

## 最後に

Qiitaでrectorの記事が5個しか無くて驚きました。
ほかには @yamii 様の記事が参考になります。

https://qiita.com/yamii/items/994e3a122eb485ebb354

rectorを使ってみた感想としては、地味ながらも、最新のPHPの記法にリファクタしてくれることを魅力に感じました。よければ、皆さんも使ってみてください。

今回は諸般の都合上、PHP8.0->8.2 とLaravel8.x->10.x にて実施しました。下位バージョンにおける動作はまだ見れていないのですが、もし使ってみた感想があれば、コメントに書いていただけるとありがたいです。

LGTMいただけると励みになります。みなさん、2024年もよいお年を！