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

By khino