【问题标题】:Validating array - get current iteration验证数组 - 获取当前迭代
【发布时间】:2020-10-02 13:46:54
【问题描述】:

我正在尝试使用 Laravel 的 FormRequest 验证 POST 请求。

客户正在提交一份订单,其中包含一系列商品。我们要求用户仅在 asking_price > 500quantity > 10 时才表明该项目是否需要 special_delivery

以下是我的预期规则:

public function rules() {
    'customer_id' => 'required|integer|exists:customers,id',
    'items' => 'required|array',
    'items.*.name' => 'required|string',
    'items.*.asking_price' => 'required|numeric',
    'items.*.quantity' => 'required|numeric',
    'items.*.special_delivery' // required if price > 500 && quantity > 10
}

我试图按照这些思路做一些事情:

Rule::requiredIf($this->input('item.*.asking_price') > 500 && $this->input('item.*.quantity' > 10));

问题在于我找不到访问当前items 迭代索引的方法来指示要验证的项目。

我还尝试了以下自定义验证:

function ($attribute, $value, $fail) {

    preg_match('/\d+/', $attribute, $m);

    $askingPrice = $this->input('items')[$m[0]]['asking_price'];
    $quantity= $this->input('items')[$m[0]]['quantity'];

    if ($askingPrice > 500 && $quantity > 10) {
        $fail("$attribute is required");
    }
}

虽然这个函数让我可以访问当前的$attribute,但问题是它只有在special_delivery 存在时才会运行。这违背了整个目的!

任何帮助将不胜感激! 谢谢!

【问题讨论】:

    标签: laravel laravel-6 laravel-validation


    【解决方案1】:

    如果你愿意的话,我可能已经想出了一个解决你的问题的方法,一个索引感知 sometimes

    由于很遗憾无法将宏添加到验证器,您要么必须重写验证工厂(这就是我的建议)并使用您自己的自定义验证类,要么根据方法创建一个辅助函数,传递验证器实例作为附加参数并使用它而不是$this

    酱汁优先:indexAwareSometimes 验证函数

    function indexAwareSometimes(
        \Illuminate\Contracts\Validation\Validator $validator,
        string $parent,
        $attribute,
        $rules,
        \Closure $callback
    ) {
        foreach (Arr::get($validator->getData(), $parent) as $index => $item) {
            if ($callback($validator->getData(), $index)) {
                foreach ((array) $attribute as $key) {
                    $path = $parent.'.'.$index.'.'.$key;
                    $validator->addRules([$path => $rules]);
                }
            }
        }
    }
    

    很多灵感显然来自sometimes method,并没有太大变化。我们基本上是在遍历包含我们所有其他数组 (items.*) 的数组($parent 数组,在您的情况下为 items),并使用实际数据进行验证并将$rulesrequired)添加到@ 987654334@ (special_delivery) 如果 $callback 评估为真,则在当前索引中。

    回调闭包需要两个参数,第一个是父验证实例的$data 形式,由Validator::getData() 检索,第二个是$index,外部foreach 在它调用回调时。

    在你的情况下,函数的用法看起来有点像这样:

    use Illuminate\Support\Arr;
    
    class YourFormRequest extends FormRequest
    {
        public function rules()
        {
            return [
                'customer_id'          => 'required|integer|exists:customers,id',
                'items'                => 'required|array',
                'items.*.name'         => 'required|string',
                'items.*.asking_price' => 'required|numeric',
                'items.*.quantity'     => 'required|numeric',
            ];
        }
    
        public function getValidatorInstance()
        {
            $validator = parent::getValidatorInstance();
    
            indexAwareSometimes(
                $validator, 
                'items',
                'special_delivery',
                'required',
                fn ($data, $index) => Arr::get($data, 'items.'.$index.'.asking_price') > 500 &&
                    Arr::get($data, 'items.'.$index.'.quantity') > 10
            );
        }
    }
    

    扩展原生 Validator

    扩展 Laravel 的原生 Validator class 并不像听起来那么难。我们正在创建一个自定义的 ValidationServiceProvider 并继承 Laravel 的 Illuminate\Validation\ValidationServiceProvider 作为父级。只有registerValidationFactory 方法需要替换为它的副本,我们指定工厂应该使用的自定义验证器解析器:

    <?php
    
    namespace App\Providers;
    
    use App\Validation\CustomValidator;
    use Illuminate\Contracts\Translation\Translator;
    use Illuminate\Validation\Factory;
    use Illuminate\Validation\ValidationServiceProvider as ParentValidationServiceProvider;
    
    class ValidationServiceProvider extends ParentValidationServiceProvider
    {
        protected function registerValidationFactory(): void
        {
            $this->app->singleton('validator', function ($app) {
                $validator = new Factory($app['translator'], $app);
    
                $resolver = function (
                    Translator $translator,
                    array $data,
                    array $rules,
                    array $messages = [],
                    array $customAttributes = []
                ) {
                    return new CustomValidator($translator, $data, $rules, $messages, $customAttributes);
                };
    
                $validator->resolver($resolver);
    
                if (isset($app['db'], $app['validation.presence'])) {
                    $validator->setPresenceVerifier($app['validation.presence']);
                }
    
                return $validator;
            });
        }
    }
    

    自定义验证器继承了 Laravel 的 Illuminate\Validation\Validator 并添加了 indexAwareSometimes 方法:

    <?php
    
    namespace App\Validation;
    
    use Closure;
    use Illuminate\Support\Arr;
    use Illuminate\Validation\Validator;
    
    class CustomValidator extends Validator
    {
        /**
         * @param  string  $parent
         * @param string|array $attribute
         * @param string|array $rules
         * @param Closure $callback
         */
        public function indexAwareSometimes(string $parent, $attribute, $rules, Closure $callback)
        {
            foreach (Arr::get($this->data, $parent) as $index => $item) {
                if ($callback($this->data, $index)) {
                    foreach ((array) $attribute as $key) {
                        $path = $parent.'.'.$index.'.'.$key;
                        $this->addRules([$path => $rules]);
                    }
                }
            }
        }
    }
    

    然后我们只需要将 Laravel 的 Illuminate\Validation\ValidationServiceProvider 替换为您自己在 config/app.php 中的自定义服务提供商,您就可以开始了。

    它甚至适用于Barry vd. Heuvel's laravel-ide-helper package

    return [
        'providers' => [
            //Illuminate\Validation\ValidationServiceProvider::class,
            App\Providers\ValidationServiceProvider::class,
        ]
    ]
    

    回到上面的例子,你只需要改变你的表单请求的getValidatorInstance()方法:

    public function getValidatorInstance()
    {
        $validator = parent::getValidatorInstance();
    
        $validator->indexAwareSometimes(
            'items',
            'special_delivery',
            'required',
            fn ($data, $index) => Arr::get($data, 'items.'.$index.'.asking_price') > 500 &&
                Arr::get($data, 'items.'.$index.'.quantity') > 10
        );
    }
    

    【讨论】:

    • 我不认为你可以调用$validator = parent::getValidatorInstance();,因为父返回函数,indexAwareSometimes 不会被调用。您可以重写整个函数,但我认为这太冒险了,通常会尽量避免这样做。我确实发现您可以在接受Illuminate\Validation\Validator 作为参数的withValidator 函数中调用indexAwareSometimes。这会自动被调用,无需覆盖其他任何内容。
    • 可以使用父方法的结果,我测试过了。很高兴它对您有所帮助!
    猜你喜欢
    • 2021-11-12
    • 2018-06-01
    • 2018-10-19
    • 2017-01-24
    • 2015-10-03
    • 1970-01-01
    • 1970-01-01
    • 2022-11-20
    • 2016-07-01
    相关资源
    最近更新 更多