

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

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

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

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

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


CC BY-SA 4.0
2021-08-12T05:08:22.677
80740
2021-11-29T02:03:52.337
2021-08-12T05:29:04.963
34267
34267
question
laravel
laravelでバリデーション全体の停止を任意のフィールドに対して指定したい


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

[code]

    <?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->stopOnFirstFailure = $stopOnFirstFailure;
    
            return $this;
        }
    
        public function passes(): bool
        {
            $this->messages = new MessageBag;
    
            [$this->distinctValues, $this->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->rules as $attribute => $rules) {
                if ($this->shouldBeExcluded($attribute)) {
                    $this->removeAttribute($attribute);
    
                    continue;
                }
    
                // ↓ここを改造
                if ($this->messages->isNotEmpty() && $this->determineStopOnFisrtFailure($attribute)) {
                    break;
                }
    
                foreach ($rules as $rule) {
                    $this->validateAttribute($attribute, $rule);
    
                    if ($this->shouldBeExcluded($attribute)) {
                        $this->removeAttribute($attribute);
    
                        break;
                    }
    
                    if ($this->shouldStopValidating($attribute)) {
                        break;
                    }
                }
            }
    
            // Here we will spin through all of the "after" 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->after as $after) {
                $after();
            }
    
            return $this->messages->isEmpty();
        }
    
        protected function determineStopOnFisrtFailure(string $attribute): bool
        {
            if (is_array($this->stopOnFirstFailure) && in_array($attribute, $this->stopOnFirstFailure, true)) {
                return true;
            }
    
            return (bool)$this->stopOnFirstFailure;
        }
    }
    
[/code]

[code]

    <?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));
        }
    }
    
[/code]

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


CC BY-SA 4.0
2021-11-29T01:57:00.280
83859
2021-11-29T02:03:52.337
2021-11-29T02:03:52.337
940
940
80740
answer