0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

LaravelのExistsバリデーションでなぜデータベースエラーが発生しないのか?

Last updated at Posted at 2019-06-21

疑問

FormRequestクラスを継承した自前のクラスで、以下のようなルールを定義した。

    public function rules()
    {
        return [
            'id' => [
                'required',
                "integer",
                "between:1,2147483647",
                'exists:users,id',
        ];
    }

このidに対して、2147483648を入力しても、データベースエラーにはならない。
また、文字列 "abcd" などを入力しても、やはりデータベースエラーにはならない。
existsより手前に設定されている検証がNGの場合に、exists検証が実行されていないようだ。
なぜ?

そういえば、requiredが失敗するとそれ以降のバリデーションが実行されていないのも理由がよくわからない。

https://readouble.com/laravel/5.5/ja/validation.html
ドキュメント中に、「暗黙の拡張」という項目があって、ここが関係していそうなのだが、ドキュメントを読んだだけでは要領を得ない感じ。

実験

そこで、自作のバリデーションルール(my_rule_exists_in_users)を作って、existsの代わりに適用してみた。
※このmy_rule_exists_in_usersは、usersテーブルのid列に、入力値が存在しているかの確認を行う処理を実装。

    public function rules()
    {
        return [
            'id' => [
                'required',
                "integer",
                "between:1,2147483647",
                //これを自作してみた
                'my_rule_exists_in_users',
        ];
    }

この場合、2147483648や"abcd"を入力すると、integer型に対して不適切な入力値であるためデータベースエラーが発生する。

なんでexistsはうまいこと動いているの?

解決編

Illuminate\Validation\Validator::validateAttribute() および、
Illuminate\Validation\Validator::isValidatable()
の実装を見ると、なせこのような動きになっているのかがわかった。

Illuminate\Validation\Validator.php(一部抜粋)
    /**
     * Determine if the attribute is validatable.
     *
     * @param  object|string  $rule
     * @param  string  $attribute
     * @param  mixed   $value
     * @return bool
     */
    protected function isValidatable($rule, $attribute, $value)
    {
        return $this->presentOrRuleIsImplicit($rule, $attribute, $value) &&
               $this->passesOptionalCheck($attribute) &&
               $this->isNotNullIfMarkedAsNullable($rule, $attribute) &&
               $this->hasNotFailedPreviousRuleIfPresenceRule($rule, $attribute);
    }

    /**
     * Determine if it's a necessary presence validation.
     *
     * This is to avoid possible database type comparison errors.
     *
     * @param  string  $rule
     * @param  string  $attribute
     * @return bool
     */
    protected function hasNotFailedPreviousRuleIfPresenceRule($rule, $attribute)
    {
        return in_array($rule, ['Unique', 'Exists']) ? ! $this->messages->has($attribute) : true;
    }

各バリデーションは、実際に検証を行う前に isValidatable() を呼んでいて、このメソッドがfalseを返すと検証自体をスキップする仕様になっているようだ。
検証ルールにrequired、nullableなどを追加している場合の動きも、この辺で実装されているっぽい。
exists の動作に直接関わっているのは↑の hasNotFailedPreviousRuleIfPresenceRule() で、ここに答えが書いてあった。

結論

自作のバリデーションルールに、existsルールのような「それより手前のバリデーションがNGの場合に検証自体をスキップする」よう振る舞ってほしい場合は、Validatorクラスを継承した自分用のValidatorクラスを定義して、hasNotFailedPreviousRuleIfPresenceRule()をオーバーライドすればよい、ということになりそう。

この['Unique', 'Exists'] を外から書き換えられるようにしておいてほしい感じではある。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?