web-dev-qa-db-fra.com

Validation de tableau imbriqué laravel

Je crée une API basée sur REST, où l'une des API a la demande suivante

{
   "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"
      }
}

Ce sont mes règles de validation

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 => [
        ]
    ];

La demande ci-dessus est correctement validée. Mais le skus peut avoir plusieurs entités à l'intérieur comme ci-dessous

{
       "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"
          }]
    }

Comment valider s'il existe plusieurs entités imbriquées?

20
Ajeesh

Quelle version de Laravel utilisez-vous? Si vous utilisez Laravel 5.2 ou si cela ne vous dérange pas de la mettre à jour, il existe une solution prête à l'emploi.

Validation de la baie

La validation des champs d'entrée de formulaire de tableau est beaucoup plus facile dans Laravel 5.2. Par exemple, pour valider que chaque e-mail dans un champ d'entrée de tableau donné est unique, vous pouvez procéder comme suit:

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

De même, vous pouvez utiliser le caractère * lorsque vous spécifiez vos messages de validation dans vos fichiers de langue, ce qui facilite l'utilisation d'un seul message de validation pour les champs basés sur les tableaux:

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

Un autre exemple de Laravel news :

Imaginez que vous avez un formulaire avec un tableau de champs de saisie comme celui-ci:

<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>

Dans Laravel 5.1 pour ajouter des règles de validation, il a fallu boucler et ajouter les règles individuellement. Au lieu d'avoir à faire tout ce qu'il a été "Laravelized" dans ceci:

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

Donc, si vous ne voulez pas utiliser Laravel 5.2, vous devrez le faire manuellement, si vous effectuez une mise à jour vers Laravel 5.2, vous pouvez utiliser le nouveau tableau validation et ce sera certains comme ceci:

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 => [
        ]
    ];

Éditer

Ihmo la meilleure façon d'ajouter cette logique de validation supplémentaire est d'étendre la classe Validator en créant votre CustomValidator classe, cela peut être un peu exagéré, mais lorsque Laravel 5.2 est publié, vous pouvez supprimer votre CustomValidator et continuer à utiliser le validateur 5.2 de Laravel sans apporter de modifications à votre code.

Comment? Eh bien, nous créons d'abord un dossier sous notre app/ J'ai décidé de nommer ce dossier Validateur vous pouvez le nommer comme vous voulez, n'oubliez pas de mettre à jour l'espace de noms des classes suivantes. Ensuite, nous allons créer 3 fichiers .php dans ce dossier CustomValidator.php , CustomValidatorServiceProvider.php et Factory.php .

CustomValidator.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;
    }
}

Ce validateur personnalisé a toutes les modifications qui ont été apportées sur Laravel 5.2, vous pouvez les archiver ici

Maintenant que nous avons une nouvelle classe CustomValidator, nous devons trouver un moyen de l'utiliser, pour cela nous devons étendre le ValidatorServiceProvider et le Usine du validateur .

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

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);
    }
}

Maintenant que nous avons étendu notre validation pour prendre en charge la syntaxe imbriquée sku.*.id

Nous avons juste à échanger le validateur à notre CustomValidator, et la dernière étape consiste à modifier le fichier config/app.php et à l'intérieur du tableau ServiceProviders, recherchez le ValidatorServiceProvider , il suffit de commenter cette ligne et d'ajouter notre fournisseur de services étendus, comme ceci:

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

La raison pour laquelle nous le commentons est parce que chaque fois que vous mettez à jour votre Laravel 5.1 à 5.2, vous voulez juste le décommenter, supprimez notre CustomValidatorServiceProvider de la liste, puis vous supprimez notre dossier app/Validator parce que nous n'en ai plus besoin.

36
Fabio Antunes