【Laravel8】ルーティングのグループ化・簡略化〜controller(),prefix(),as(),group()の使用〜

## 背景
業務でLaravelのroutes/web.phpの記述量が増えてきた際、Route::の記述を少しでも簡潔に書けないかということで、業務上で得た知見を記録として残しておきます。


### 動作環境
 - Laravel8.83.12
 - PHP8.1.5

### 使用する状況の例
個別のユーザーに対し、何らかのチャットを表示・送信・削除する機能を実装することを想定します。
ユーザー一覧/個別ユーザーのID/チャットページ（表示、送信、削除）
のようなイメージです。

### 結論
今回の実装ができるようになると、以下のメリットが得られます。
 - 同じControllerを使用する場合、Routeが使用するControllerを重複して記述する必要がなくなります
 - ルートパラメータを含むURIの記述を、一部簡略化できます
 - 名前付きルート（ルートの名前）で使用する->name()メソッド内の記述を、一部簡略化できます

具体的な書き方は以下の通りです。
~~~php:routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserChatController;

Route::controller(UserChatController::class)
  ->prefix('user/{userId}/chat')
  ->as('user.chat.')
  ->group(function(){
    Route::get('/' , 'index')->name('index')->where('userId' , '[0-9]+');
    Route::post('/send' , 'send')->name('send')->where('userId' , '[0-9]+');
    Route::delete('/delete' , 'delete')->name('delete')->where('userId' , '[0-9]+');
  });
~~~

ここまでご覧になってご理解いただければ、以下は補足説明になります。
必要に応じてご覧ください。



### Routeの基本的な書き方
~~~php:routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserChatController;

Route::get('/user/{userId}/chat' , [UserChatController::class , 'index'])->name('user.chat.index')->where('userId' , '[0-9]+');
Route::post('/user/{userId}/chat/send' , [UserChatController::class , 'send'])->name('user.chat.send')->where('userId' , '[0-9]+');
Route::delete('/user/{userId}/chat/delete' , [UserChatController::class , 'delete'])->name('user.chat.delete')->where('userId' , '[0-9]+');
~~~

特に意識せず基本的な書き方に従った場合は、この様な記述内容になるのではないでしょうか。
私も例に漏れず、最初はこのような書き方をしていました。
ただこの書き方ですと`/user/{userId}/chat`、`[UserChatController::class]`、`->name('user.chat.~~~')`の箇所が冗長になります。
あるURIに対する処理が増えてきた場合、同じ記述が増えるため、シンプルさを保つことが難しくなります。
あと単純に文章が長いですね。

これらの課題を解決する記述方法が、以下に記載した方法になります。


### グループ化・簡略化の具体例
~~~php:routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserChatController;

Route::controller(UserChatController::class)
  ->prefix('user/{userId}/chat')
  ->as('user.chat.')
  ->group(function(){
    Route::get('/' , 'index')->name('index')->where('userId' , '[0-9]+');
    Route::post('/send' , 'send')->name('send')->where('userId' , '[0-9]+');
    Route::delete('/delete' , 'delete')->name('delete')->where('userId' , '[0-9]+');
  });
~~~

#### `Route::controller(UserController::class)`　の箇所
グループ化するControllerを指定します。
以降、指定したControllerを使用するURIに関しては、後述するgroup()メソッドの引数に渡すクロージャー内に記述すれば、Controllerを重複して書く必要がなくなります。


#### `->prefix('user/{userId}/chat')`　の箇所
URIのプレフィックスを指定します。
ここでは`user/{userId}/chat`までがURIに共通する箇所なので、prefix()の引数に渡しています。
共通しない箇所は後述するgroup()メソッド内のクロージャに記載することで、URIに応じた処理が実行可能になります。
今回のように、URIにルートパラメータ（ここではuserId）がある場合も、prefix内に`user/{userId}/chat`として渡すことが可能です。
渡す引数を`'user/{userId}/chat/'`のように末尾に`/`をつけた場合は、クロージャー内の各Routeは以下の様に変更されます。
~~~php
    Route::get('' , 'index')->name('index')->where('userId' , '[0-9]+');
    Route::post('send' , 'send')->name('send')->where('userId' , '[0-9]+');
    Route::delete('delete' , 'delete')->name('delete')->where('userId' , '[0-9]+');
~~~



#### `->as('user.chat.')`　の箇所
名前付きルートの共通箇所を指定します。
name()メソッド内で重複する内容は、as()メソッド内に集約可能です。
ちなみに引数の最後の`.`は必ず記載してください。
記載したとおりにルート名のプレフィックスとして使用されます。
チャット送信するフォームを例にすると、以下の違いが発生します。
~~~php:resources/views/user/chat/index.blade.php
//as('user.chat.')の場合
<form method="post" action="{{ route('user.chat.send', ['userId' => $user->id]) }}">
//as('user.chat')と、引数の末尾の「.」を記載しなかった場合、route()メソッドの第一引数を'user.chatsend'にしないとエラーになる
<form method="post" action="{{ route('user.chatsend', ['userId' => $user->id]) }}">
~~~

#### `->group(function(){});`　の箇所
Controllerでグループ化するRouteを、group()メソッド内のクロージャーに渡します。
今までチェーンで繋いだメソッド内に抽出されている箇所は記述が不要になるため、group()メソッド内は以下の記述となります。

~~~php
  ->group(function(){
    Route::get('/' , 'index')->name('index')->where('userId' , '[0-9]+');
    Route::post('/send' , 'send')->name('send')->where('userId' , '[0-9]+');
    Route::delete('/delete' , 'delete')->name('delete')->where('userId' , '[0-9]+');
  });
~~~

執筆中に`->where('userId' , '[0-9]+')`の部分も重複しているので抽出できないかと思いましたが、試してみたところTypeErrorが発生しました。
よってwhere()メソッドはgroup()メソッドのクロージャー内に重複して残っています。
改善方法がわかれば随時更新したします。




以上、少しでも参考になれば幸いです。




### 参考・引用
 - [Define a Route Group Controller in Laravel 8.80](URL:https://laravel-news.com/laravel-8-80-0)
 - [Laravel 8.x ルーティング](URL:https://readouble.com/laravel/8.x/ja/routing.html)