1つのコントローラの中で、バリデーションに使用するルールをメソッド間で共有したい、とは誰しも思うこと。私もLaravelを習得して以来、管理性が高くしかもすっきりとした方法を求め続けていました。前回で紹介したカスタムルールの登場で、これは使えるんじゃない、という方法を見つけましたので、ここにて披露です。
欲しいもの
いくつか条件あります。
1.コントローラー内でルールを指定したい
これはどちらかというと実用性より好みの問題かもしれませんが、Form Requestのように違うファイルとして定義するのではなく、コントローラー内で定義することにより、あちこちのファイルを見に行くのではなくコントローラーのファイル内で使用されるルールを見たいためです。たいていのコントローラーは、そうたいした数のルールがあるわけでもないので、Form Requestを使用する必要もないです。
2.コントローラーのメソッド内でルールを指定したくない
これは管理性大いにあります。以下のようにそれぞれのメソッド内で重複してルールを定義するのは間違いが起こるもとです。もちろん、これが今回のルールの共有の大きな要因でもあります。
namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Validation\Rule; use App\User; class UserController extends Controller { ... public function store(Request $request) { $request->validate([ 'name' => 'required', 'name_kana' => 'required|kana', 'mailcode' => 'required|mailcode', 'prefecture' => 'required', 'city' => 'required', 'address1' => 'required', 'phone' => 'required|phone_with_dash', 'email' => 'required|email|unique:users', 'password' => 'required|min:8|max:20|confirmed', ]); ... } ... public function update(Request $request, User $user) { $request->validate([ 'name' => 'required', 'name_kana' => 'required|kana', 'mailcode' => 'required|mailcode', 'prefecture' => 'required', 'city' => 'required', 'address1' => 'required', 'phone' => 'required|phone_with_dash', 'email' => [ 'required', 'email', Rule::unique('users')->ignore($user->id), //現在のレコード以外のレコードで重複がないかチェック ], ]); ... } ... }
解決方法 その1
上の条件にかなうものとしては、コントローラー内の一箇所にルールーを指定することになります。まず、考えたのは、インスタンス変数を使用すること、
namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Validation\Rule; use App\User; class UserController extends Controller { protected $rules = [ 'shared' => [ // kana, mailcode, phone_with_dashはカスタムバリデーター 'name' => 'required', 'name_kana' => 'required|kana', 'mailcode' => 'required|mailcode', 'prefecture' => 'required', 'city' => 'required', 'address1' => 'required', 'phone' => 'required|phone_with_dash', ], 'store' => [ 'email' => 'required|email|unique:users', 'password' => 'required|min:8|max:20|confirmed', ], 'update' => [ 'email' => [ 'required', 'email', Rule::unique('users')->ignore($user->id), //現在のレコード以外のレコードで重複がないかチェック ], ] ]; ... public function store(Request $request) { $request->validate($this->rules['shared'] + $this->rules['store']); ... } ... public function update(Request $request, User $user) { $request->validate($this->rules['shared'] + $this->rules['update']); ... } ... }
いい感じ?
しかし問題あります。
Rule::unique('users')->ignore($user->id)
これ、$rules
の変数の宣言時に、このような初期化はできないし、$user
を渡すこともできません。update()
のメソッド内でその初期化をする必要あります。
... public function update(Request $request, User $user) { $thus->rules['update']['email'][] = Rule::unique('users')->ignore($user->id); $request->validate($this->rules['shared'] + $this->rules['update']); ... } ...
せっかくコントローラーの先頭で一箇所で定義しようと思ったのに、これは良くないですね。
欲しかった解決方法
初期化が問題なら、クラスのインスタンス変数の代わりに、Form Requestにあるように、rules()
という名前でコントローラーの関数にしてはどうでしょう、ということで以下になりました。
namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Validation\Rule; use App\User; class UserController extends Controller { public function rule($action, User $user = null) { // 共有するルール kana, mailcode, phone_with_dashはカスタムバリデーター $rules = [ 'name' => 'required', 'name_kana' => 'required|kana', 'mailcode' => 'required|mailcode', 'prefecture' => 'required', 'city' => 'required', 'address1' => 'required', 'phone' => 'required|phone_with_dash', ]; // 共有しないルール switch ($action) { case 'store': // レコード作成のためのルールを追加 $rules['password'] = 'required|min:8|max:20|confirmed'; $rules['email'] = 'required|email|unique:users'; break; case 'update': // レコード編集のためのルールを追加 $rules['email'] = [ 'required', 'email', Rule::unique('users')->ignore($user->id), //現在のレコード以外のレコードで重複がないかチェック ]; break; } return [ // rules $rules, // messages [ 'password.min' => '8から20文字長でお願いします', 'password.max' => '8から20文字長でお願いします' ] // attributes ]; } ... public function store(Request $request) { $request->validate(...$this->rules('store')); ... } ... public function update(Request $request, User $user) { $request->validate(...$this->rules('update', $user)); ... } ... }
rules()
の関数には、2つの引数があります。最初の$action
はメソッド名で、それにより返す連想配列$rules
を特定し、次の$user
は編集で必要とされる値(ここでは重複のチェックのためにレコードid)を取得のためが目的です。
関数の引数は状況に応じて自由に変えてください。以下は、前回に紹介したカスタムルールの例ですが、同じ入力画面からの違う項目のdate_start
の値を取得しています。
public function rules($action, Request $request) { // store $rules = [ 'date_start' => 'date', 'date_end' => [ 'date', 'after_or_equal:date_start', new RestrictPeriodRule($request->date_start), 6) ], ]; return [$rules]; }
最後に、rules()
が返す配列は、$request->validate()
の引数にマッチする、rules、messages、attributesとなりますが、必要に応じてrulesだけでもOKです。このように不特定数の引数があるときは、コールするときに、 $request->validate(...$this->rules('store'))
のようにphp5.6より登場した3つのドットを使って渡します。