以前に、Laravel 5.3 コントローラのコンストラクタの重要な変更として、コントローラで定義するメソッド間で共有するコードをコンストラクタに入れることが可能なことを説明しました。今度は同じコンストラクタ内で、コントローラのメソッドに渡される引数の取り出しかたを説明します。何を言っているかというと、まずは準備から。
準備
例として、routes/web.phpにおいて、以下のようなrouteを定義します。
... Route::resource('product', 'ProductController'); ...
これは、以下のようなURIを生成します。
+--------+-----------+-------------------------------+-----------------------+------------------------------------------------------------------------+----------------------------------------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+-----------+-------------------------------+-----------------------+------------------------------------------------------------------------+----------------------------------------------+ | | GET|HEAD | product | product.index | App\Http\Controllers\ProductController@index | web | | | POST | product | product.store | App\Http\Controllers\ProductController@store | web | | | GET|HEAD | product/create | product.create | App\Http\Controllers\ProductController@create | web | | | GET|HEAD | product/{product} | product.show | App\Http\Controllers\ProductController@show | web | | | PUT|PATCH | product/{product} | product.update | App\Http\Controllers\ProductController@update | web | | | DELETE | product/{product} | product.destroy | App\Http\Controllers\ProductController@destroy | web | | | GET|HEAD | product/{product}/edit | product.edit | App\Http\Controllers\ProductController@edit | web | +--------+-----------+-------------------------------+-----------------------+------------------------------------------------------------------------+----------------------------------------------+
そして、ProductController.phpを以下のように定義します。
namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Product; class ProductController extends Controller { /** * 情報画面 * * @param \App\Models\Product $product * @return \Illuminate\View\View */ public function show(Product $product) { // } /** * 編集画面 * * @param \App\Models\Product $product * @return \Illuminate\View\View */ public function edit(Product $product) { // } /** * 編集保存 * * @param \Illuminate\Http\Request $request * @param \App\Models\Product $product * @return \Illuminate\Http\RedirectResponse */ public function update(Request $request, Product $product) { // } }
コンストラクタで引数を取り出す
用意ができたところで、
例えば、
product/456
のGETのアクセスでは、id = 456に対応するProductのEloquentオブジェクトがshow()
のメソッドの引数$product
の値として渡されます。
そして、
product/456/edit
のGETは、id = 456に対応するProductのオブジェクトがedit()
のメソッドの引数$product
の値として渡されます。
さて、このid = 456のProductのオブジェクト、それぞれのメソッドの引数と渡される前に、コンストラクタで取り出すには?
namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Product; class ProductController extends Controller { public function __construct() { parent::__construct(); $this->middleware(function ($request, $next) { $product = $request->product; //id = 456のProductのオブジェクトを取得 debug(get_class($product), $product->id); return $next($request); }); } ...
と、middleware()
の関数を使えば、その匿名関数の中で簡単に取り出せますね!
product/456
のURIにブラウザでアクセスすれば、Debugbarでは、
debug App\Models\Product debug 456
とデバッグ文を表示します。
product/456/edit
でも
product/456/update
でも、同じようにそれぞれのメソッドを実行する前に、引数にアクセスすることが可能となります。
ひとつここで疑うのは、Productのオブジェクトを取得するために以下のようなSQL文が、middleware()
で1回、そしてさらにshow()
のようなメソッドの引数でもう1回、計2回実行されることになるか、です。
select * from product where id = 456
これもDebugbarのQueryの出力で1回しか実行されていないことを確認しました。一度作成したオブジェクトを同じコントローラ内で2度作成することはなりません。
取り出して、それからどうする?
ここが大事なのですが、例えば、この商品(product)がEコマースで販売される商品とします。商品の登録や編集は、裏側の管理画面で商品の製造元の店舗がログインして行います。しかし、複数の店舗が存在するので、店舗Aが店舗Bの商品を編集できては困ります。しかし、URIには商品のIDが表示されるので、IDの数字を変えて他の店舗の商品を閲覧(公開されていない情報がある)や編集することが可能となってしまいます。それを防ぐためには、
... public function __construct() { parent::__construct(); $this->middleware(function ($request, $next) { $product = $request->product; //id = 456のProductのオブジェクトを取得 $user = auth('shop')->user(); // 現在ログインしている店舗のユーザー if ($product->shop_id != $user->shop_id) { //この商品の店舗ではない! abort(404); } return $next($request); }); } ...
のように設定すれば、いちいちshow()
やedit()
やupdate()
などで、同様なコードを繰り返さずに済みプログラムの管理性が高まる、ということです。