EDIT UPDATE処理を通して
ROUTE MODEL BINDING
ルートモデルバインディングってすごい
お話をします。
ROUTE MODEL BINDINGとは
URLのIDからMODELを自動取得してくれるLaravelの仕組み
ROUTE MODEL BINDINGのここがすごい
1:コントローラーがシンプルに!!!
2:安全性が高い!!!
見つからない時に404エラーを返してくれるので
エラー処理書かなくてもOK
3:可読性が高い!!!
「Product $product」と書くだけで「Productモデルが渡ってくる!」
と一目でわかる!!!!
4:公式が推奨している!!!!!
公式ドキュメント盛りだくさん!!!!
実際に書いてみた
ここはPHPのフレームワークであるLaravelと、
フロントエンド開発ツールのInertia.jsを組み合わせて、
商品の編集画面(ページ)を表示する処理をするところ
フロントはVueで書いてます
(index初めその他のコントローラーについては省略)
// ProductsController
namespace App\Http\Controllers;
use App\Http\Requests\ProductRequest;
use App\Models\Product;
use Inertia\Inertia;
// ここは商品管理画面の表示をするページ用のコントローラー
public function show(Product $product)
{
return Inertia::render('Products/Show', [
'product' => $product,
]);
}
具体的には、以下の3つの処理を同時に行っている。
1:データの自動取得(ルートモデルバインディング)
引数の Product $product によって、
URLに含まれるID(例: /products/5/edit の 5)に対応する商品データを、
データベースから自動的に1件取得
2:画面(コンポーネント)の指定
Inertia::render('Products/Edit', ...)で
フロントエンド(Vue.jsやReactなど)の
resources/js/Pages/Products/Edit にある
画面ファイルを表示するように指示
3:フロントエンドへデータを渡す第2引数を設定
'product' => $product と指定することで、
自動取得した商品データをフロントエンドの画面側に
「props(プロパティ)」として渡している
これをしているから
編集画面を開いたときに最初からその商品の名前や価格が入力欄に表示される
なぜ書かなくていいのか?
Laravelのルートモデルバインディング(Route Model Binding)
という強力な機能が、
裏側で自動的に find() 相当の処理を行ってくれているから
URLからの推測:この edit メソッドを呼び出すルーティング(URLの設定)は
Route::get('/products/{product}/edit', ...) のようになっている
URLの `{product} の部分に、商品のID(例: 5)が入り
型指定による自動化:引数で Product $product と型を指定しているため
Laravelが「URLの変数 {product} は、Product モデルのID(5)のことだな」
と自動で解釈(えらい)
自動でデータベース検索:Laravelが裏側で自動的に Product::findOrFail(5) を実行
見つかった商品データを $product に入れた状態でこの edit メソッドを呼び出し
開発者がわざわざ Product::find($id) を手動で書く必要がなくなっている
もし手動で書く場合 コードが長くなる
// ルートモデルバインディングを使わない場合(IDを直接受け取る)
php
public function edit($id)
{
// 手動でデータベースから探す必要がある
$product = Product::findOrFail($id);
return Inertia::render('Products/Edit', [
'product' => $product,
]);
}
手動の場合、
1:IDを受け取る
2:自分でMODELを検索する必要がある
3:コードが長くなりやすい
これがROUTEMODELBINDINGだと
自動化されているおかげで、コードがシンプルで読みやすくなる
もしURLで指定されたIDの商品がデータベースに存在しない場合は、
自動的に404エラー(Not Found)画面を返してくれる
また、この仕組み(ルートモデルバインディング)を動かすには
routes/web.php(ルーティング)の設定が正しく連動している必要があるから注意
実際に書いてみた(EDIT UPDATE)
// editでProductモデルを受け取りますという型と
// ↓それを入れる箱である変数 $productを一行目で定義
public function edit(Product $product)
{
// ↓まさかのここでもう既にMODELが渡されている!!!!!!!!!!!
return Inertia::render('Products/Edit', [
'product' => $product,
]);
}
public function update(ProductRequest $request, Product $product)
{
// 1. バリデーション済みの安全なデータのみを取得
$validated = $request->validated();
// 2. 安全なデータのみでモデルを更新
$product->update($validated);
// 一覧画面へリダイレクト
return redirect()->route('products.index')
->with('success', '商品の内容を更新しました');
}
Laravelの仕様上、第一引数はフォームリクエストとして動作
(ProductRequest $requestのとこ)
コントローラ側で「どのデータを安全に扱うか」の意図が曖昧になりがち
セキュリティ(意図しないデータの更新)$request->validated() は、
ProductRequest の rules() で定義した項目だけを抽出
// ProductRequest 一部抜粋
class ProductRequest extends FormRequest
{
//(中略)
public function rules(): array
{
return [
'name' => 'required|max:100',
'price' => 'required|integer',
'stock' => 'required|integer',
'description' => 'max:200',
];
}
public function messages(): array
{
return [
'name.required' => '商品名を入力してください',
'name.max' => '100文字以内で入力してください',
'price.required' => '価格を入力してください',
'price.integer' => '半角数字で入力してください',
'stock.required' => '在庫数を入力してください',
'stock.integer' => '半角数字で入力してください。',
'description.max' => '200文字以内で入力してください',
];
}
}
リクエストデータの受け取り方によっては
IDが変更されたり管理者フラグなどが書き換えられるリスク(マスアサインメント脆弱性)
が残るので注意
→リクエストデータをそのままデータベースに流し込んでいるだけになる
Mass Assignment(大量代入)
Webアプリケーションが外部から受け取ったデータを、
オブジェクトの属性に一括で代入する機能
便利な反面、意図しない属性まで書き換えられる可能性があり、
セキュリティ上の重大なリスクとなる。