こんにちは、かつコーチです。
Laravelも長く使っていて相棒のような存在担ってきました。
Laravelを使っていると、Routeの書き方で迷うことがあります。
Laravelは自由度が高く、同じことを実現する方法がいくつもあります。
そのため、小規模なプロジェクトでは問題なくても、システムが大きくなるにつれて「どの書き方に統一するべきか」「Route名が重複してしまった」「あとから見返すと分かりにくい」と感じることが増えてきます。
今回は、自分自身の備忘録も兼ねて、LaravelでのRoute設計について整理してみます。
Routeの役割は3つだけ
まず一番大切なのは、Routeの役割を理解することです。
Routeの役割は基本的に次の3つしかありません。
- URLを決める
- HTTPメソッド(GET・POST・PUT・DELETEなど)を決める
- Controllerへ処理を渡す
つまり、
- データ取得
- 業務ロジック
- バリデーション
- Serviceの呼び出し
などを書く場所ではありません。
Routeはあくまで「橋渡し役」と考えると設計がシンプルになります。
まずはResource Routeを基本にする
Laravelには便利なResource Routeがあります。
Route::resource('admins', AdminController::class);
これだけで以下の7つのルートが自動生成されます。
| HTTP | URL | Action |
|---|---|---|
| GET | /admins | index |
| GET | /admins/create | create |
| POST | /admins | store |
| GET | /admins/{admin} | show |
| GET | /admins/{admin}/edit | edit |
| PUT/PATCH | /admins/{admin} | update |
| DELETE | /admins/{admin} | destroy |
この7つはLaravelの標準的なCRUDです。
会社管理、顧客管理、商品管理など、一般的な管理画面はほとんどこれだけで十分です。
そのため、
CRUDは必ずResourceを使う
というルールにしてしまうと迷いません。
CRUDだけでは足りないことも多い
実際の業務システムではCRUDだけでは終わりません。
例えば請求書管理なら
- 送信
- PDF出力
- 支払い登録
- 期限切れ一覧
などがあります。
このような処理はResourceには存在しません。
例えば、
Route::get('/overdue', [InvoiceController::class, 'overdueList'])->name('overdue');
Route::patch('/{invoice}/send', [InvoiceController::class, 'send'])->name('send');
Route::post('/{invoice}/payments', [InvoiceController::class, 'recordPayment'])->name('payments.store');
Route::get('/{invoice}/pdf', [InvoiceController::class, 'downloadPdf'])->name('pdf');
この書き方でも問題ありません。
ただしController名を何度も書くため、Routeが増えると少し見づらくなります。
Controller Groupを使うと見やすくなる
LaravelではControllerをまとめることができます。
Route::controller(InvoiceController::class)
->group(function () {
Route::get('/overdue', 'overdueList')->name('overdue');
Route::patch('/{invoice}/send', 'send')->name('send');
Route::post('/{invoice}/payments', 'recordPayment')->name('payments.store');
Route::get('/{invoice}/pdf', 'downloadPdf')->name('pdf');
});
Controller名を一度しか書かなくて済むため、こちらの方が可読性は高くなります。
個人的にはこちらの書き方が好みです。
prefix・name・controllerをセットで使う
さらにおすすめなのがこちらです。
Route::prefix('invoices')
->name('invoices.')
->controller(InvoiceController::class)
->group(function () {
Route::get('/overdue', 'overdueList')
->name('overdue');
Route::patch('/{invoice}/send', 'send')
->name('send');
Route::get('/{invoice}/pdf', 'downloadPdf')
->name('pdf');
});
するとRoute名は
invoices.overdue
invoices.send
invoices.pdf
となります。
URLもRoute名も整理されるので、大規模システムでは非常におすすめです。
ResourceとController Groupを組み合わせる
個人的にはこの組み合わせが一番分かりやすいと思います。
Route::resource('invoices', InvoiceController::class);
Route::prefix('invoices')
->name('invoices.')
->controller(InvoiceController::class)
->group(function () {
Route::get('/overdue', 'overdueList')
->name('overdue');
Route::patch('/{invoice}/send', 'send')
->name('send');
Route::get('/{invoice}/pdf', 'downloadPdf')
->name('pdf');
});
役割が非常に明確になります。
- CRUD → Resource
- 特殊な機能 → Group
という住み分けができます。
URLはRESTを意識する
LaravelではRESTを意識したURLにすると迷いません。
例えば
/invoice/send
よりも
/invoices/{invoice}/send
の方が分かりやすいです。
URLを見ただけで
「InvoiceのID10を送信する」
という意味が伝わります。
Route Parameterは{id}ではなくModel名
Laravelでは
/{invoice}
のように書くことをおすすめします。
{id}
でも動きますが、
public function show(Invoice $invoice)
と書くだけでRoute Model Bindingが利用できます。
Model名にしておく方がLaravelらしい設計になります。
一番困るのはRoute名の重複
大規模なシステムを作っていると、一番困るのがRoute名の重複です。
例えば
Route::resource('companies', CompanyController::class);
この時点でLaravelは
companies.index
companies.create
companies.store
companies.show
companies.edit
companies.update
companies.destroy
を自動生成しています。
ここで
Route::name('companies.')
->group(function () {
Route::get('/search', ...)
->name('index');
});
としてしまうと
companies.index
が重複してしまいます。
Laravelでは後から定義したRouteが優先されるため、
route('companies.index')
が意図しないRouteになってしまいます。
このエラーは意外と気付きにくく、原因調査に時間がかかることがあります。
Route名のルールを決める
重複を防ぐためには命名規則を決めてしまうのが一番です。
例えば
| 用途 | Route名 |
|---|---|
| 一覧 | companies.index |
| 検索 | companies.search |
| companies.pdf | |
| CSV出力 | companies.export |
| CSV取込 | companies.import |
| 一括削除 | companies.bulk-delete |
| 複製 | companies.duplicate |
このように決めておくと、Route名が衝突する可能性はかなり低くなります。
route:listを確認する習慣
Routeを追加したら
php artisan route:list
を見る癖を付けると安心です。
さらに
php artisan route:list --name=companies
とすると対象だけ表示できます。
Route名の確認にも非常に便利です。
私が採用したいRoute設計ルール
最終的に、自分の中では次のように整理しました。
① CRUDは必ずResource
Route::resource('companies', CompanyController::class);
② 特殊機能はGroup
Route::prefix('companies')
->name('companies.')
->controller(CompanyController::class)
->group(function () {
Route::get('/search', 'search')->name('search');
Route::get('/export', 'export')->name('export');
Route::post('/import', 'import')->name('import');
});
③ URLはRESTを意識する
/companies/{company}
/companies/{company}/pdf
/contracts/{contract}/items
④ Route名は階層化する
companies.index
companies.search
companies.export
contracts.items.store
contracts.items.update
contracts.items.destroy
⑤ Route ParameterはModel名
{company}
{contract}
{invoice}
まとめ
LaravelはRouteの自由度が高い反面、プロジェクトが大きくなるほど「統一ルール」が重要になります。
私自身もこれまで何度かRoute名の重複や、Controllerごとに書き方がバラバラになってしまい、後から見返して迷うことがありました。
今後は次の方針で統一していこうと思います。
- CRUDは
Route::resource()を使う - CRUD以外は
prefix + name + controller + groupにまとめる - URLはRESTを意識する
- Route名は階層化して重複を防ぐ
- Route Parameterは
{id}ではなく{company}のようにModel名を使う
このルールを決めておくだけで、Routeファイルはかなり読みやすくなり、保守性も大きく向上します。
Laravelは自由だからこそ、「自分なりのルール」を決めることが、長く開発を続ける上で一番大切だと感じています。
コメント