以前に、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()などで、同様なコードを繰り返さずに済みプログラムの管理性が高まる、ということです。
