LaravelのRoute設計で迷わないための整理術 〜Resource・Controller Group・命名規則の考え方〜

laravelアイキャッチ Laravel

こんにちは、かつコーチです。

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つのルートが自動生成されます。

HTTPURLAction
GET/adminsindex
GET/admins/createcreate
POST/adminsstore
GET/admins/{admin}show
GET/admins/{admin}/editedit
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
PDFcompanies.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は自由だからこそ、「自分なりのルール」を決めることが、長く開発を続ける上で一番大切だと感じています。

コメント

タイトルとURLをコピーしました