Laravel 10から11のアップグレードで全ファイル変更してみた話
Laravel11では設定項目やデフォルト値、Kernelクラス廃止など多くの構成変更がされています。
10以前の構成でも問題なく動くように設計されているため、通常は新たな構成に合わせての大規模な変更を必要としません。
今回は以下ようなのケースで小規模なプロダクトだったので全部置き換える力業が可能で勉強がてら全更新することにしました。
- 個人開発
- Laravel9, PHP8.0あたりから依存パッケージのメンテだけで放置気味
- 機能数は多くなく、さらに半分ぐらい切り捨てられそう(50クラス未満)
- 静的解析ツールlarastan(phpstan), リファクタツールrecotrなどを併用してモダンなコードに書き換えたい
- メンテが楽になる環境にしたい
計画
大まかなステップとしては以下です。
- 既存機能の洗い出し
- 改修、削除対象を決める
- Laravel11ベース構築
- pint
- ide-helper
- larastan
- rector
- 旧コードからの移植、リファクタ
- テスト再整備
- プロダクション適用
実行
既存機能の洗い出し
まずはコードを見て機能を洗い出します。
Model, Service, Controller, Console, 実データベースあたりを見て構造を把握しました。
改修、削除対象を決める
洗い出した機能の中から改修して使用し続けるものを決めます。
移行対象が多くなるほどハードルが上がるため、必要と明言できないものは切り捨てます。
もし切り捨てたけどやっぱり必要…となればgitの履歴を遡って追加移植できるので問題なしと判断しました。
Laravel11ベース構築
旧来のコードを一旦 /old
に退避させます。
別リポジトリとしないのは退避させたファイルをそのまま移植する可能性もあるのでgitの履歴が切れないようにするためです。
参考: git mv
公式の手順に従ってセットアップしてきます。(省略)
pint
Laravelセットアップ時に自動で導入されています。
デフォルトのままでも問題ないですが declare(strict_types=1);
を必須にする declare_strict_types
、必ずクラスにfinalつけて残念な継承を防げる final_class
、メソッドの順序などを指定する ordered_*
などを追加で有効にしました。
{
"preset": "laravel",
"rules": {
"declare_strict_types": true,
"protected_to_private":true,
"class_reference_name_casing":true,
"final_public_method_for_abstract_class":true,
"ordered_class_elements":{"order": [
"use_trait",
"case",
"constant_public",
"constant_protected",
"constant_private",
"property_public",
"property_protected",
"property_private",
"construct",
"destruct",
"magic",
"phpunit",
"method_public",
"method_protected",
"method_private"
]},
"ordered_interfaces":{"order": "alpha"},
"ordered_traits":{"case_sensitive": false},
"ordered_types":{"case_sensitive": false},
"self_accessor":true,
"self_static_accessor":true,
"visibility_required":{"elements": ["const","method","property"]},
"final_class":true
},
"exclude": [
]
}
ide-helper
Eloquentモデルやファサードなどの型支援を受けられるように ide-helper を導入します。
php artisan ide-helper:generate
モデルの情報はモデルクラスでなく、専用ファイル _ide_helper_models.php
へ書き出す方式しました。
コンフリクトが起きても専用ファイル削除->再生成で対応できます。
php artisan ide-helper:models -WR
モデル変更などに合わせてcomposer スクリプトで都度実行できるようにしておくと便利です。
larastan
phpstanのLaravel対応版である larastan を導入します。
ゆるふわPHP絶対殺すマンになるつもりでLv.9にします。
経験上一度maxまで上げてしまえば、後々の改修ではそれほど苦労しないです。
includes:
- vendor/larastan/larastan/extension.neon
parameters:
paths:
- app/
# Level 9 is the highest level
level: 9
rector
rector を導入します。
phpバージョンはphp8.3、オプション全部載せにしました。
場合によってはコードが壊れるので項目ごとに有効化していき様子を見るのが良いです。
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([
__DIR__.'/app',
__DIR__.'/bootstrap',
__DIR__.'/public',
__DIR__.'/resources',
__DIR__.'/routes',
__DIR__.'/tests',
])
->withPhpSets(php83: true)
->withPreparedSets(
deadCode: true,
codeQuality: true,
codingStyle: true,
typeDeclarations: true,
privatization: true,
naming: true,
instanceOf: true,
earlyReturn: true,
strictBooleans: true,
)
->withSkip([]);
旧コードからの移植、リファクタ
機能ごとに以下のステップで移植しました。
テストケースの整備は最小限にしました。
責務分離して1ファイル(クラス)ごとのロジックを単純化することで考慮漏れを減らすことができると判断したためです。
- 退避させたファイルを移植予定のディレクトリに移動、必要ならリネーム
- コミットしてファイル移動として記録
- コード(クラス)を責務過剰とならないよう修正、分割etc
- 仕様が複雑で今後変更可能性のある場所はテストケース作成
- rector, larastanを実行してコードを整える
クラス構成は一般的なMVC、Laravelの構成のままですがビジネスロジックは大幅に構成変更しました。
機能ごとに作成していたServiceクラスをで目的別のActionsクラスへ集約しました。
具体的には以下のイメージです。
Services/記事サービス
新規作成メソッド
変更メソッド
リレーション更新(共通利用するプライベートメソッド)
...
↓
Actions/記事/
新規作成クラス
変更クラス
リレーション更新クラス
...
これにより新規作成クラスは更新時やリレーションのことを考えずに実装、テストケースの管理が可能になりました。
プライベートメソッドであったリレーション処理もクラス化したため単体テストも可能です。
参考 Laravel Actionsパッケージ
参考 Laravel fortifyのアクションクラス
テスト再整備
一通り移行し終わったら結合テスト、開発環境で動作検証しつつ不具合を確認していきます。
問題の発生した個所は条件洗い出しもかねて単体テストを実装します。
プロダクション適用
動作検証が済めばリリースするだけです。
切り戻しの可能性も想定し不要テーブル削除や環境変数の更新などはせず、アプリケーションのみの変更にとどめることで通常のリリースフローで適用できました。
結果
古い仕様のしがらみからも解放され、最新版のドキュメントを読み込んで実践することもでき、非常に満足な結果になりました。
この記事では触れませんでしたがフロントエンドはvue2(laravel mix)->livewireに変えています。フロントエンド(nodejs)は移り変わりが早く、livewireに置き換えることでほぼjsレスにできたこともメリットでした。
Discussion