Laravel の FormRequest で、2つのうち片方を必須にするバリデーションをかける


HTTP リクエストの入力パラメーターが2つあって、どちらも空の場合は許容しないバリデーションを実装する方法を調べてみました。

今回は p1 と p2 という GET パラメーターを受けて、両方指定がない場合はエラーを出す複合条件を作成することを目標とします。ここでは、値が空文字(?p1=)とパラメーター指定なし(
?)は等価です。Laravel 6.x で動作確認しました。

    public function action(ExperimentalRequest $request)
    {
        return ['passes' => true];
    }

コントローラーのメソッドはこんな感じで、 ExperimentalRequestという Request クラスを実装するイメージです。(ルーティングとかは省略します)

方法1

required_without を使います。指定したパラメーターが空だった場合は必須チェックをする、という標準のバリデーションルールです。下記例では p2 が空だった場合は p1 の必須チェックを行います。

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\JsonResponse;
use Illuminate\Contracts\Validation\Validator;

class ExperimentalRequest extends FormRequest
{
    public function rules()
    {
        return [
            'p1' => 'required_without:p2',
            'p2' => '',
        ];
    }

    /**
     * バリデーションエラーがある場合 JSON を返却する(動作確認用)
     */
    protected function failedValidation(Validator $validator)
    {
        throw new HttpResponseException(new JsonResponse($validator->errors(), 422));
    }
}

required_without ルールは両方のパラメーターに指定したくなるかもしれませんが、片方に指定すれば十分です。

ただこの方法だと、両方値が設定されている場合も OK になってしまいます。どちらか一方のみにしたい場合は少し工夫が必要です。

p2 なしp2=1
p1 なし×
p1=1
○はチェック通過
?
{"p1":["The p1 field is required when p2 is not present."]}

?p1=1
{"passes":true}

?p2=1
{"passes":true}

?p1=1&p2=1
{"passes":true}

方法2

両方空、両方指定ありを弾く自前のバリデーションを書いてみます。排他的論理和ですね。

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\JsonResponse;
use Illuminate\Contracts\Validation\Validator;

class ExperimentalRequest extends FormRequest
{
    public function rules()
    {
        return [];
    }

    public function withValidator(Validator $validator)
    {
        $validator->after(function ($validator) {
            if ($this->filled(['p1', 'p2'])) {
                $validator->errors()->add(
                    'p1', 'p1 と p2 は両方指定できません。どちらか一方を指定してください。'
                );
            } elseif (!$this->anyFilled(['p1', 'p2'])) {
                $validator->errors()->add(
                    'p1', 'p1 と p2 が空です。どちらか一方を指定してください。'
                );
            }
        });
    }

    /**
     * バリデーションエラーがある場合 JSON を返却する(動作確認用)
     */
    protected function failedValidation(Validator $validator)
    {
        throw new HttpResponseException(new JsonResponse($validator->errors(), 422));
    }
}

FormRequest で withValidator メソッドを実装すると、親クラスが Validator を初期化した後に呼んでくれます。Validator はバリデーション処理をもろもろやってくれるやつです。after メソッドでコールバック関数を渡し、 Validator にフックをかけます。 バリデーション完了直後にこのコールバック関数を呼んでくれます。

結果は以下のようになります。

p2 なしp2=1
p1 なし×
p1=1×
両方指定した場合もエラーになる
?
{"p1":["p1 と p2 が空です。どちらか一方を指定してください。"]}

?p1=1
{"passes":true}

?p2=1
{"passes":true}

?p1=1&p2=1
{"p1":["p1 と p2 は両方指定できません。どちらか一方を指定してください。"]}

自作のバリデーションを他の Request でも再利用したい場合は extension として定義することも可能です。

まとめ

after hook を使えば、大抵のバリデーションは書けると思います。色々なフレームワーク触ってきましたが、 Laravel のバリデーションは簡単に書けるのがいいですね。


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください