扩展/覆盖 Laravel 验证器类

Posted

技术标签:

【中文标题】扩展/覆盖 Laravel 验证器类【英文标题】:Extending/Overriding Laravel Validator Class 【发布时间】:2021-06-07 05:13:01 【问题描述】:

在 Laravel 8.3 中,他们引入了一个新功能,stopOnFirstFailure,一旦规则失败,它就会完全停止验证。我想在 Laravel 7 中使用此功能。在检查 Laravel 8 的 vendor/laravel/framework/src/Validation/Validator.php 时,我发现 stopOnFirstFailure 只是在 Validator.phppasses 函数中添加一个 if 语句> 如果受保护的变量 stopOnFirstFailure 为真,则会中断验证循环。是否可以通过扩展/覆盖 Validator.php 类在 Laravel 7 中实现这些?我一直在研究扩展核心 Laravel 类并偶然发现了这个 article 但这有点令人困惑,因为这篇文章只展示了如何覆盖特定的函数。在我的情况下,我需要声明一个受保护的变量,覆盖一个函数并声明一个新函数。

Laravel 8 Validator.php 代码: 声明变量:

/**
     * Indicates if the validator should stop on the first rule failure.
     *
     * @var bool
     */
    protected $stopOnFirstFailure = false;

stopOnFirstFailure功能:

  /**
     * Instruct the validator to stop validating after the first rule failure.
     *
     * @param  bool  $stopOnFirstFailure
     * @return $this
     */
    public function stopOnFirstFailure($stopOnFirstFailure = true)
    
        $this->stopOnFirstFailure = $stopOnFirstFailure;

        return $this;
    

通过功能:

/**
     * Determine if the data passes the validation rules.
     *
     * @return bool
     */
    public function passes()
    
        $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->stopOnFirstFailure && $this->messages->isNotEmpty()) 
                break;
            

            foreach ($rules as $rule) 
                $this->validateAttribute($attribute, $rule);

                if ($this->shouldBeExcluded($attribute)) 
                    $this->removeAttribute($attribute);

                    break;
                

                if ($this->shouldStopValidating($attribute)) 
                    break;
                
            
        

编辑:表单请求在我的代码中使用了验证器。 我的代码示例:

class UpdateRegistrationTagsRequest extends FormRequest

    protected $stopOnFirstFailure = true;
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    
        return true;
    

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    
        return [
            'product_id' => ['required'],
            'product.*.type' => ['required','distinct'],
            'product.*.value' => ['required'],
            'product' => ['bail', 'required', 'array', new CheckIfArrayOfObj, new CheckIfProductTypeExists($this->product_id)]
        ];
    

    protected function failedValidation(\Illuminate\Contracts\Validation\Validator $validator)
    
        $response = new JsonResponse(['api' => [
            'header' => [
                'message' => 'The given data is invalid', 
                'errors' => $validator->errors()->first()   
            ],
            'body' => ''
                ]], 422);

        throw new \Illuminate\Validation\ValidationException($validator, $response);
    


编辑:遵循@thefallen 的建议,这就是我所做的。 我的 CustomValidator.php 类在 app 目录内的 CustomClass 中:

<?php 

namespace App\CustomClass;
use Illuminate\Validation\Validator;
use Illuminate\Support\MessageBag;

class CustomValidator extends Validator

    /**
     * Indicates if the validator should stop on the first rule failure.
     *
     * @var bool
     */
    protected $stopOnFirstFailure = true;

     /**
     * Instruct the validator to stop validating after the first rule failure.
     *
     * @param  bool  $stopOnFirstFailure
     * @return $this
     */
    public function stopOnFirstFailure($stopOnFirstFailure = true)
    
        $this->stopOnFirstFailure = $stopOnFirstFailure;

        return $this;
    

    /**
     * Determine if the data passes the validation rules.
     *
     * @return bool
     */
    public function passes()
    
        $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->stopOnFirstFailure && $this->messages->isNotEmpty()) 
                break;
            

            foreach ($rules as $rule) 
                $this->validateAttribute($attribute, $rule);

                if ($this->shouldBeExcluded($attribute)) 
                    $this->removeAttribute($attribute);

                    break;
                

                if ($this->shouldStopValidating($attribute)) 
                    break;
                
            
        
        return parent::passes();
    

CustomClass 文件夹中的我的 ValidatorFactory

<?php 

namespace App\CustomClass;
use Illuminate\Validation\Factory;
use App\CustomClass\CustomValidator;

class ValidatorFactory extends Factory

    protected function resolve( array $data, array $rules, array $messages, array $customAttributes )
    
        if (is_null($this->resolver)) 
            return new CustomValidator($this->translator, $data, $rules, $messages, $customAttributes);
        

        return call_user_func($this->resolver, $this->translator, $data, $rules, $messages, $customAttributes);
    

我的 AppServiceProvider:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\CustomClass\ValidatorFactory;

class AppServiceProvider extends ServiceProvider

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    
        $this->app->extend('validator', function () 
            return $this->app->get(ValidatorFactory::class);
        );
    

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    
        //
    


【问题讨论】:

你能添加代码你目前如何使用验证器吗?你有 Request 类还是手动调用它? @thefallen 嗨,添加了我的代码。谢谢。 Would it be possible to implement these in Laravel 7 by extending/overiding the Validator.php class? 是的,我想这是你的问题 【参考方案1】:

您基本上需要扩展 Validator 以对该方法进行更改,然后创建自己的 Validation Factory 来创建这个新的 Validator 而不是默认的。所以第一步是使用你自己的验证器:

use Illuminate\Validation\Validator;

class CustomValidator extends Validator

    public function passes()
    
        //TODO make changes on that loop
        return parent::passes();
    

然后你需要一个验证工厂来创建这个新类,这也将扩展默认类:

use Illuminate\Validation\Factory;

class ValidatorFactory extends Factory

    protected function resolve( array $data, array $rules, array $messages, array $customAttributes )
    
        if (is_null($this->resolver)) 
            return new CustomValidator($this->translator, $data, $rules, $messages, $customAttributes);
        

        return call_user_func($this->resolver, $this->translator, $data, $rules, $messages, $customAttributes);
    

最后,在register() 方法中的app/Providers/AppServiceProvider.php 中,您需要将默认工厂替换为自定义工厂:

$this->app->extend('validator', function () 
    return $this->app->get(ValidatorFactory::class);
);

注意validatorIlluminate\Validation\Factory 的绑定(或别名)的名称。你应该很高兴能够对验证器进行任何更改。

【讨论】:

感谢您的回复@thefallen。我尝试了你的建议,我在我的应用程序目录中创建了一个名为 CustomClass 的文件夹,然后创建了两个类 CustomValidator.php 和 ValidatorFactory.php。我在 CustomValidator.php 中复制了 Laravel 8 代码,并为 ValidatorFactory 复制了您的代码,并在 AppServiceProvider 中注册了工厂,但它似乎不起作用,但我没有收到任何错误。 @cjdy13,这很奇怪,因为如果您按照步骤操作,这应该适用于表单请求和手动验证。无论如何,我不想建议的另一个解决方案是在您的表单请求类中有一个validator() 方法,您可以从那里返回您的类app(CustomValidator::class)。缺点是只会为此表单请求调用。 让我更新我的帖子,这样我就可以向你展示我所做的一切。 我用当前代码更新了我的帖子。谢谢! 嗨@thefallen,我的错,我复制的passes函数不完整,我只是复制了丢失的代码,现在它可以工作了!非常感谢您的帮助! :)【参考方案2】:

可能有点晚了,但我在 Laravel 6 上遇到了同样的问题。 不想扩展/覆盖验证器的当前正常行为。 所以我这样做了

public function validateResolved()

    if (!$this->authorize()) 
        $this->failedAuthorization();
    
    foreach ($this->rules() as $key=>$val) 
        $validator = app('validator')->make(
            request()->all(),
            [$key=>$val],
            $this->messages()
        );
        if ($validator->fails()) 
            throw \Illuminate\Validation\ValidationException::withMessages($validator->errors()->messages());
        
    

【讨论】:

以上是关于扩展/覆盖 Laravel 验证器类的主要内容,如果未能解决你的问题,请参考以下文章

多次扩展 Laravel 验证器

Laravel同时显示多种语言验证错误

11.Laravel5学习笔记:扩展 Validator 类

如何覆盖验证规则登录 Laravel\Fortify?

覆盖 laravel 验证消息

使用身份验证护照系统覆盖 laravel 中 apis url 的身份验证重定向