嵌套数组验证 laravel

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/34393279/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 12:50:50  来源:igfitidea点击:

Nested array validation laravel

phpvalidationlaravellaravel-5

提问by Ajeesh

I am building a REST based API, where one of the API is having the following request

我正在构建一个基于 REST 的 API,其中一个 API 有以下请求

{
   "categories_id" :"1",
   "product_name" : "Pen",
   "product_description" : "this is pen",
   "tags" : "pen,write",
   "image_count" : "4",
   "skus": 
      {
          "is_shippable":"n",
          "actual_price":"100.55", 
          "selling_price":"200.45",
          "quantity_type":"bucket",
          "quantity_total":"10",
          "bucket_value":"instock",
          "sort_order":"1"
      }
}

These are my validation rules

这些是我的验证规则

protected $rules = [
        ValidatorInterface::RULE_CREATE => [
        'users_id' => 'required',
        'user_profiles_id' => 'required',
        'categories_id' => 'required',
        'product_name' => 'required|max:100',
        'product_description' => 'required|max:1000',
        'tags' => 'required',
        'image_count'=>'required|integer',
        'creation_mode'=>'required|integer',
        'skus.is_shippable'=>'in:y,n',
        'skus.actual_price'=>'regex:/^\s*(?=.*[1-9])\d*(?:\.\d{1,2})?\s*$/',
        'skus.selling_price' => 'regex:/^\s*(?=.*[1-9])\d*(?:\.\d{1,2})?\s*$/',
        'skus.quantity_type' => 'sometimes|required|in:finite,infinite,bucket',
        'skus.quantity_total' => 'integer|required_if:skus.quantity_type,finite', 
        'skus.bucket_value'=>'in:instock,soldout,limited|required_if:skus.quantity_type,bucket',
        'skus.sort_order'=> 'required|integer'
        ],
        ValidatorInterface::RULE_UPDATE => [
        ]
    ];

The above request is properly getting validated. But The skus can have multiple entities inside like below request

上述请求已正确得到验证。但是 skus 内部可以有多个实体,如下面的请求

{
       "categories_id" :"1",
       "product_name" : "Pen",
       "product_description" : "this is pen",
       "tags" : "pen,write",
       "image_count" : "4",
       "skus": 
          [{
              "is_shippable":"n",
              "actual_price":"100.55", 
              "selling_price":"200.45",
              "quantity_type":"bucket",
              "quantity_total":"10",
              "bucket_value":"instock",
              "sort_order":"1"
          },
          {
              "is_shippable":"n",
              "actual_price":"100.55", 
              "selling_price":"200.45",
              "quantity_type":"bucket",
              "quantity_total":"10",
              "bucket_value":"instock",
              "sort_order":"1"
          }]
    }

How do I validate if there are multiple nested entities?

如何验证是否有多个嵌套实体?

回答by Fabio Antunes

What version of Laravel are you using? If you are using Laravel 5.2or if you don't mind updating to it, there's a solution out of the box.

你用的是什么版本的 Laravel?如果您使用的是Laravel 5.2或者您不介意更新到它,那么这里有一个开箱即用的解决方案。

Array Validation

Validating array form input fields is much easier in Laravel 5.2. For example, to validate that each e-mail in a given array input field is unique, you may do the following:

数组验证

在 Laravel 5.2 中验证数组表单输入字段要容易得多。例如,要验证给定数组输入字段中的每封电子邮件都是唯一的,您可以执行以下操作:

$validator = Validator::make($request->all(), [
    'person.*.email' => 'email|unique:users'
]);

Likewise, you may use the * character when specifying your validation messages in your language files, making it a breeze to use a single validation message for array based fields:

同样,您可以在语言文件中指定验证消息时使用 * 字符,这使得对基于数组的字段使用单个验证消息变得轻而易举:

'custom' => [
    'person.*.email' => [
        'unique' => 'Each person must have a unique e-mail address',
    ]
],

Another example from Laravel news:

另一个来自Laravel 新闻的例子:

Pretend you have a form with an array of input fields like this:

假设您有一个包含一系列输入字段的表单,如下所示:

<p>
<input type="text" name="person[1][id]">
<input type="text" name="person[1][name]">
</p>
<p>
<input type="text" name="person[2][id]">
<input type="text" name="person[2][name]">
</p>

In Laravel 5.1 to add validation rules it required looping through and adding the rules individually. Instead of having to do all that it's been “Laravelized” into this:

在 Laravel 5.1 中添加验证规则需要循环并单独添加规则。不必做所有已经“Laravelized”的事情:

$v = Validator::make($request->all(), [
  'person.*.id' => 'exists:users.id',
  'person.*.name' => 'required:string',
]);

So if you don't want to use Laravel 5.2 you will have to do it manually, if you do update to Laravel 5.2 you can use the new array validation and it will be some like this:

因此,如果您不想使用 Laravel 5.2,则必须手动进行,如果您更新到 Laravel 5.2,则可以使用新的数组验证,它会是这样的:

protected $rules = [
        ValidatorInterface::RULE_CREATE => [
        'users_id' => 'required',
        'user_profiles_id' => 'required',
        'categories_id' => 'required',
        'product_name' => 'required|max:100',
        'product_description' => 'required|max:1000',
        'tags' => 'required',
        'image_count'=>'required|integer',
        'creation_mode'=>'required|integer',
        'skus.*.is_shippable'=>'in:y,n',
        'skus.*.actual_price'=>'regex:/^\s*(?=.*[1-9])\d*(?:\.\d{1,2})?\s*$/',
        'skus.*.selling_price' => 'regex:/^\s*(?=.*[1-9])\d*(?:\.\d{1,2})?\s*$/',
        'skus.*.quantity_type' => 'sometimes|required|in:finite,infinite,bucket',
        'skus.*.quantity_total' => 'integer|required_if:skus.quantity_type,finite', 
        'skus.*.bucket_value'=>'in:instock,soldout,limited|required_if:skus.quantity_type,bucket',
        'skus.*.sort_order'=> 'required|integer'
        ],
        ValidatorInterface::RULE_UPDATE => [
        ]
    ];

Edit

编辑

Ihmo the best way to add this extra validation logic is extending the Validatorclass creating your CustomValidatorclass, it may be a bit overkill, but when Laravel 5.2 gets released you can remove your CustomValidator and keep on using Laravel's 5.2 Validator without making any changes to your code.

Ihmo 添加此额外验证逻辑的最佳方法是扩展Validator类创建您的CustomValidator类,这可能有点矫枉过正,但是当 Laravel 5.2 发布时,您可以删除您的 CustomValidator 并继续使用 Laravel 的 5.2 验证器,而无需对你的代码。

How? Well first we create a folder under our app/I decided to name this folder Validatoryou can name it whatever you want, just remember to update the namespace of the following classes. Next we are going to create 3 .php files inside this folder CustomValidator.php, CustomValidatorServiceProvider.phpand Factory.php.

如何?好吧,首先我们在我们的下创建一个文件夹,app/我决定将这个文件夹命名为Validator,您可以随意命名,只需记住更新以下类的命名空间即可。接下来,我们将在此文件夹中创建 3 个 .php 文件CustomValidator.phpCustomValidatorServiceProvider.phpFactory.php

CustomValidator.php

自定义验证器.php

<?php

namespace App\Validator;

use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Validation\Validator;
use Symfony\Component\Translation\TranslatorInterface;

class CustomValidator extends Validator
{
    /**
     * Create a new Validator instance.
     *
     * @param  \Symfony\Component\Translation\TranslatorInterface  $translator
     * @param  array  $data
     * @param  array  $rules
     * @param  array  $messages
     * @param  array  $customAttributes
     * @return void
     */
    public function __construct(TranslatorInterface $translator, array $data, array $rules, array $messages = [], array $customAttributes = [])
    {
        $this->translator = $translator;
        $this->customMessages = $messages;
        $this->data = $this->parseData($data);
        $this->customAttributes = $customAttributes;

        // Explode the rules first so that the implicit ->each calls are made...
        $rules = $this->explodeRules($rules);

        $this->rules = array_merge((array) $this->rules, $rules);
    }

    /**
     * Explode the rules into an array of rules.
     *
     * @param  string|array  $rules
     * @return array
     */
    protected function explodeRules($rules)
    {
        foreach ($rules as $key => $rule) {
            if (Str::contains($key, '*')) {
                $this->each($key, $rule);
                unset($rules[$key]);
            } else {
                $rules[$key] = (is_string($rule)) ? explode('|', $rule) : $rule;
            }
        }
        return $rules;
    }


    /**
     * Define a set of rules that apply to each element in an array attribute.
     *
     * @param  string  $attribute
     * @param  string|array  $rules
     * @return void
     *
     * @throws \InvalidArgumentException
     */
    public function each($attribute, $rules)
    {
        $data = Arr::dot($this->data);
        foreach ($data as $key => $value) {
            if (Str::startsWith($key, $attribute) || Str::is($attribute, $key)) {
                foreach ((array) $rules as $ruleKey => $ruleValue) {
                    if (! is_string($ruleKey) || Str::endsWith($key, $ruleKey)) {
                        $this->mergeRules($key, $ruleValue);
                    }
                }
            }
        }
    }



    /**
     * Get the inline message for a rule if it exists.
     *
     * @param  string  $attribute
     * @param  string  $lowerRule
     * @param  array   $source
     * @return string|null
     */
    protected function getInlineMessage($attribute, $lowerRule, $source = null)
    {
        $source = $source ?: $this->customMessages;
        $keys = ["{$attribute}.{$lowerRule}", $lowerRule];
        // First we will check for a custom message for an attribute specific rule
        // message for the fields, then we will check for a general custom line
        // that is not attribute specific. If we find either we'll return it.
        foreach ($keys as $key) {
            foreach (array_keys($source) as $sourceKey) {
                if (Str::is($sourceKey, $key)) {
                    return $source[$sourceKey];
                }
            }
        }
    }

    /**
     * Get the custom error message from translator.
     *
     * @param  string  $customKey
     * @return string
     */
    protected function getCustomMessageFromTranslator($customKey)
    {
        $shortKey = str_replace('validation.custom.', '', $customKey);
        $customMessages = Arr::dot(
            (array) $this->translator->trans('validation.custom')
        );
        foreach ($customMessages as $key => $message) {
            if ($key === $shortKey || (Str::contains($key, ['*']) && Str::is($key, $shortKey))) {
                return $message;
            }
        }
        return $customKey;
    }
}

This custom validator has all the changes that were made on Laravel 5.2, you can check them in here

这个自定义验证器包含在 Laravel 5.2 上所做的所有更改,您可以在此处查看

Now since we have a new CustomValidator class we have to find a way of using it, for that we have to extend the ValidatorServiceProviderand the Validator factory.

现在因为我们有了一个新的 CustomValidator 类,我们必须找到一种使用它的方法,为此我们必须扩展ValidatorServiceProviderValidator factory

CustomValidatorServiceProvider.php

CustomValidatorServiceProvider.php

<?php

namespace App\Validator;


class CustomValidatorServiceProvider extends \Illuminate\Validation\ValidationServiceProvider
{
    /**
     * Register the validation factory.
     *
     * @return void
     */
    protected function registerValidationFactory()
    {
        $this->app->singleton('validator', function ($app) {
            $validator = new Factory($app['translator'], $app);

            // The validation presence verifier is responsible for determining the existence
            // of values in a given data collection, typically a relational database or
            // other persistent data stores. And it is used to check for uniqueness.
            if (isset($app['validation.presence'])) {
                $validator->setPresenceVerifier($app['validation.presence']);
            }

            return $validator;
        });
    }
}

Factory.php

工厂.php

<?php

namespace App\Validator;

use App\Validator\CustomValidator as Validator;

class Factory extends \Illuminate\Validation\Factory
{
    /**
     * Resolve a new Validator instance.
     *
     * @param  array  $data
     * @param  array  $rules
     * @param  array  $messages
     * @param  array  $customAttributes
     * @return App\Test\CustomValidator
     */
    protected function resolve(array $data, array $rules, array $messages, array $customAttributes)
    {
        if (is_null($this->resolver)) {
            return new Validator($this->translator, $data, $rules, $messages, $customAttributes);
        }

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

Now that we have extended our validation to support the nested syntax sku.*.id

现在我们已经扩展了验证以支持嵌套语法 sku.*.id

We just have to swap the Validator to our CustomValidator, and the last step is changing the file config/app.phpand inside the ServiceProviders array look for the ValidatorServiceProvider, just comment that line and add our extended service provider, like this:

我们只需要将 Validator 交换到我们的 CustomValidator,最后一步是更改文件config/app.php并在 ServiceProviders 数组中查找ValidatorServiceProvider,只需注释该行并添加我们的扩展服务提供者,如下所示:

....
// Illuminate\Validation\ValidationServiceProvider::class,
App\Validator\CustomValidatorServiceProvider::class,
....

The reason we are commenting it out is because whenever you update your Laravel 5.1 to 5.2 you just want to uncomment it, remove our CustomValidatorServiceProvider from the list and then you delete our app/Validator folder because we don't need it anymore.

我们将其注释掉的原因是,每当您将 Laravel 5.1 更新到 5.2 时,您只想取消注释,从列表中删除我们的 CustomValidatorServiceProvider,然后删除我们的 app/Validator 文件夹,因为我们不再需要它了。