入力画面のバリデーションでちょいと頭をひねったケースです。2つの入力項目があり、どちらかが必須なのだけれど両方の入力は禁止という場合です。まず、具体的な例を見てみましょう。

簡単化しましたが、要はお客さんに配るクーポンのレコードの作成です。

注目してもらいたのは「割引」の項目で、2つの入力場所があります。ひとつは割引額で円で入力、もうひとつは割引率で%で入力です。

コントローラのコードはこんな感じです。新規のDBレコード作成の部分だけですけれど。

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class CouponController extends Controller
{
    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
  		return view('coupon.create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
    	$rules = [
            'code'            => 'required|alpha_numeric',
            'discount_amount' => 'nullable|required_without:discount_rate|integer|gt:0',
    	    'discount_rate'   => 'nullable|required_without:discount_amount|integer|gt:0|lte:100',
    	];

    	$validated = $request->validate($rules);

        // DBに保存のコードがここに入る
    }
...

discount_amount(割引額)と discount_rate(割引率)のバリデーションに注目してください。nullableは両方とも空になるかもしれないゆえに必要です。

次の、required_withoutは指定する項目が空のとき(つまりnull)はバリデーションの対象の項目が必須となる、ということです。両方の項目でお互いの項目を指定しているのは、両方が空となっては困るからです。つまり、どちらかの入力が必須となります。

よさそうですね。

と思いきや、この問題は両方に値を入れてもパスしてしまうことです。

その問題に対応するために思いついたのは、以下のようにバリデーションに匿名の関数を使用します。

...
   	$rules = [
            'code'            => 'required|alpha_num',
            'discount_amount' => [
                'nullable',
                'required_without:discount_rate',
                function ($attribute, $value, $fail) {
                    if (request('discount_rate')) {
                        $fail("割引額あるいは割引率どちらかだけ入力してください");
                    }
                },
                'integer',
                'gt:0',
            ],
    		'discount_rate'   => 'nullable|required_without:discount_amount|integer|gt:0|lte:100',
    	];
...

request()のヘルパーがここで役に立ちます。バリデーション対象以外の項目の値を簡単に取得できます。

最後に、今回の「どちらかに入力してください」のケースはそう起こることではないと思いますが、UIを変えてクリエイティブな対応も可能と思います。例えば、以下のように割引額と割引率をラジオボタンとすると、それぞれに対応する値の入力項目は2つでなく1つになり常に必須とすることできます。もちろん、それはそれでバリデーションのルールが簡単になるかどうかは保証なしですけれど。

By khino