1

laravelのバリデーションなのですが、他のフィールドのバリデーションに影響与える重要なフィールドをまず最初に行い、もしだめならバリデーション全体を停止したいとします。

仮にその重要なフィールドをAとするとAが正しいという前提でB、Cなどのフィールドをカスタムバリデーションしたりしています。

laravelでバリデーションを途中停止させる方法にはbailstopOnFirstFailureの二つがありますが、bailは、そのフィールドは停止しても次のフィールドでバリデーションを続けてしまい、stopOnFirstFailureは全体停止しますが、フィールド指定ができずどのフィールドでも停止してしまいます。

欲しいのはbailのように各フィールドに指定できて、stopOnFirstFailureのように全体停止するものなのですが、こういったことを行う方法はないでしょうか。

現在自分が行っている対策として、フォームリクエスト内のrules()でわざわざValidator::make()を行ってルール追加を制御するということをやっているのですが、見苦しく保守性が悪いので何かシンプルな方法があればコードが綺麗になって助かります。

anlovean
  • 189
  • 1
  • 3
  • 12

1 Answers1

1

今のところ標準機能としては存在しないので,ベタ書きよりもマシになるように,自分で拡張して DRY 性を高めるぐらいの工夫しかできないと思います。

<?php

namespace App\Extensions\Validation;

use Illuminate\Validation\Validator as BaseValidator;

class Validator extends BaseValidator { /** * @var bool|string[] */ protected $stopOnFirstFailure = false;

/**
 * @param  bool|string[] $stopOnFirstFailure
 * @return $this
 */
public function stopOnFirstFailure($stopOnFirstFailure = true)
{
    $this-&gt;stopOnFirstFailure = $stopOnFirstFailure;

    return $this;
}

public function passes(): bool
{
    $this-&gt;messages = new MessageBag;

    [$this-&gt;distinctValues, $this-&gt;failedRules] = [[], []];

    // We'll spin through each rule, validating the attributes attached to that
    // rule. Any error messages will be added to the containers with each of
    // the other error messages, returning true if we don't have messages.
    foreach ($this-&gt;rules as $attribute =&gt; $rules) {
        if ($this-&gt;shouldBeExcluded($attribute)) {
            $this-&gt;removeAttribute($attribute);

            continue;
        }

        // ↓ここを改造
        if ($this-&gt;messages-&gt;isNotEmpty() &amp;&amp; $this-&gt;determineStopOnFisrtFailure($attribute)) {
            break;
        }

        foreach ($rules as $rule) {
            $this-&gt;validateAttribute($attribute, $rule);

            if ($this-&gt;shouldBeExcluded($attribute)) {
                $this-&gt;removeAttribute($attribute);

                break;
            }

            if ($this-&gt;shouldStopValidating($attribute)) {
                break;
            }
        }
    }

    // Here we will spin through all of the &quot;after&quot; hooks on this validator and
    // fire them off. This gives the callbacks a chance to perform all kinds
    // of other validation that needs to get wrapped up in this operation.
    foreach ($this-&gt;after as $after) {
        $after();
    }

    return $this-&gt;messages-&gt;isEmpty();
}

protected function determineStopOnFisrtFailure(string $attribute): bool
{
    if (is_array($this-&gt;stopOnFirstFailure) &amp;&amp; in_array($attribute, $this-&gt;stopOnFirstFailure, true)) {
        return true;
    }

    return (bool)$this-&gt;stopOnFirstFailure;
}

}

<?php

namespace App\Extensions\Validation;

use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Factory;

class ValidationServiceProvider extends ServiceProvider
{
    public function boot(Factory $factory): void
    {
        $factory->resolver(fn (...$args) => new Validator(...$args));
    }
}

これをサービスプロバイダに登録すれば,自分で用意した拡張 Validator クラスが使用されるので,stopOnFirstFailure の対象に string[] を指定することができるようになるでしょう。

mpyw
  • 606
  • 4
  • 14
  • この機能,とくに大きく後方互換性が壊れることも無さそうなので,プルリクエストを Laravel 9.x に向けて出してみてはどうでしょうか?採用されそうな気もします – mpyw Nov 29 '21 at 01:57