(気付けば前回の投稿から丸1年経ってた・・・)
皆さん、Laravel使っていますか?
筆者は現在Typescriptがメインの案件についているのですが
たまにLaravelも触ります。
リクエストを受けてからデータの存在チェックをする、なんてのはよくあるケースなんですが
今回初めて知ったものがあるのでメモがてらアウトプットします。
従来のよくある実装パターン
例えば「記事id(post_id)」から記事情報を取得し、詳細データを画面に表示する機能があるとします。
class PostController extends Controller
{
public function edit(PostEditRequest $request)
{
$detail = Post::where('id', $request->post_id)->first();
return view('post', ['data' => $detail]);
}
}
と書いておいて、FormRequest側で
class PostEditRequest extends FormRequest
{
public function rules()
{
return [
'post_id' => 'required|exists:posts,id'
];
}
}
みたいな感じ。あるいは、ルーティング側で
Route::get('/post/{id}', [PostController::class, 'edit']);
のようにして
class PostController extends Controller
{
public function edit(int $id)
{
$detail = Post::where('id', $id)->first();
if (is_null($detail)) {
// エラーで返す処理
}
return view('post', ['data' => $detail]);
}
}
のようなパターンもあるでしょう。
ルートモデルバインディングとは
今回知ったのは「ルートモデルバインディング」という仕組みで
結構前のバージョンから存在しているようです。(何故知らんのだ?というツッコミはお控えくださいw)
これを使うと
Route::get('/post/{post}', [PostController::class, 'edit']);
のようにルーティング設定しておき
class PostController extends Controller
{
public function edit(Post $post)
{
return view('post', ['data' => $post]);
}
}
という風にすれば、Controllerに入った時点でEloquent Modelのインスタンスができるんですね~。
しかも存在しないidの場合、404エラーを返してくれるというオマケ付き!
主キー以外のカラムで検索する
さらに
Route::get('/post/{post:post_uuid}', [PostController::class, 'edit']);
のように書けば他のカラムに対して検索し、あればController処理を行うし、無ければ404になる。
idのバリデーションチェックをしたいけど、FormRequestを実装するのは大変で
シンプルな実装でも良い場合は、こちらの手法が使えるかもしれませんね。
リレーション関係のモデルをバインディングする
おまけですが、リレーション関係にあるデータをルートモデルバインディングで拾う場合
例えば「会社(Company)」に紐づく「従業員(Employee)」のデータを照会するようなケース
Route::get('/company/{company}/employee/{employee}', [EmployeeController::class, 'detail'])->scopeBindings();
とすればcompanyに紐づくemployeeのモデルを持った状態でController処理を行えます。
受け取り方は
class EmployeeController extends Controller
{
public function detail(Company $company, Employee $employee)
{
// 何かの処理
}
}
ただ、この方法を使う場合は、LaravelのEloquent ORMが親子関係を理解する必要があるため
予めCompanyモデルに
class Company extends Model
{
public function employees(): HasMany
{
return $this->hasMany(Employee::class);
}
}
のようにリレーショナルメソッドを用意しておく必要がある。
(他の方法でも親子関係を構築すればできるけど、今回は割愛)
まとめ
- ルートモデルバインディングを使うとControllerに入った時点でModelインスタンスがある状態になる
- バリデーション実装が緩くても良いならシンプルに実装できる
- 存在しないデータの場合は自動で404エラーを返してくれる
- 主キー以外のカラムでも検索できる
- 親子関係のModelも
scopeBindings()を使えばルートモデルバインディングできる- これにより、指定されたemployeeが本当にそのcompanyに属しているかを自動チェックしてくれる
リソース取得と存在確認のそれぞれのアプローチ
注:FormRequestとルートモデルバインディングはそれぞれ異なる概念で、併用が可能です。
ここで伝えたいのは、あくまで同じような処理(リソース取得・存在確認)に対し、それぞれどういうアプローチで処理するかについてです
- FormRequestを使う場合: 複雑なバリデーションルールがある、カスタムエラーメッセージが必要
- ルートモデルバインディングを使う場合: シンプルな存在チェックだけで十分、コードをスッキリさせたい
如何だったでしょうか?
実装方法の選択肢が増えると、状況に応じた最適な実装ができるようになりますね。
皆さんもぜひ試してみてください!