skip to Main Content

I want to access information within nested validation array to do a custom validation, here is my code:

return [
...,
'amount' => ['nullable', 'numeric'],
'currency_id' => ['nullable', 'integer', Rule::in($allowedCurrencyIds)],
'details' => ['nullable'],
'details.*.product_id' => ['required', 'integer', 'exists:products,id'],
'details.*.product_collection_id' => [
   'nullable',
   'integer',
   'exists:product_collections,id',
   new ProductAndCollectionValidator($this->details.*.->product_id, $this->details->product_collection_id)
],
...
]

As you can see I want to access the product id and the product collection id with details.* to send to the custom validatior ProductAndCollectionValidator. And I need to concider that product_collection_id might be null sometimes. And that is the first step.

Second step I want to make sure that there are no duplicate product_id and product_collection_id the the details array

How can I do that?

2

Answers


  1. you can use Laravel’s custom validation rules and validation closures. https://laravel.com/docs/11.x/validation#using-closures. Try this on your validator:

    return [
        // Other validation rules...
        'details.*.product_id' => ['required', 'integer', 'exists:products,id'],
        'details.*.product_collection_id' => [
            'nullable',
            'integer',
            'exists:product_collections,id',
            function ($attribute, $value, $fail) {
                // Accessing nested values within details array
                $productId = $this->input('details')[$this->getIndexFromAttribute($attribute)]['product_id'];
                // Custom validation logic using ProductAndCollectionValidator
                $validator = new ProductAndCollectionValidator($productId, $value);
                if (!$validator->passes()) {
                    $fail($validator->message());
                }
            }
        ],
        'details' => ['nullable', function ($attribute, $value, $fail) {
            // Custom validation rule to check for duplicates
            $uniqueDetails = collect($value)->unique(function ($detail) {
                return $detail['product_id'] . '-' . $detail['product_collection_id'];
            });
            if ($uniqueDetails->count() !== count($value)) {
                $fail('Duplicate product_id and product_collection_id combinations found.');
            }
        }],
    ];
    
    
    function getIndexFromAttribute($attribute)
    {
        return explode('.', str_replace(['[*]', '*'], ['.', ''], $attribute))[1];
    }
    

    Let me know. Cheers.

    Login or Signup to reply.
  2. Here are 2 solutions. I didn’t tested. Please let me know if there is an error.

    1. You could create a single custom rule for data array:

    Request:

    return [
        ...,
        'amount' => ['nullable', 'numeric'],
        'currency_id' => ['nullable', 'integer', Rule::in($allowedCurrencyIds)],
        'details' => ['nullable', 'array', new ArrayUniqueItems(['product_id', 'product_collection_id'])],
        'details.*.product_id' => ['required', 'integer', 'exists:products,id'],
        'details.*.product_collection_id' => ['nullable', 'integer', 'exists:product_collections,id'],
        ...
    ];
    

    Rule:

    <?php
    
    namespace AppRules;
    
    use IlluminateContractsValidationValidationRule;
    
    class ArrayUniqueItems implements ValidationRule
    {
        public function __construct(private arrray $keys) {}
    
        public function __invoke($attribute, $value, $fail): void
        {
            foreach ($keys as $key) {
                collect(collect($value)->duplicates($key))
                    ->each(fn ($item, $index) => $fail(
                        "$attribute.$index.product_id", 
                        "Duplicate $key"),
                    );
            }
        }
    }
    
    1. You could create a custom rule data items:

    Request:

    return [
        ...,
        'amount' => ['nullable', 'numeric'],
        'currency_id' => ['nullable', 'integer', Rule::in($allowedCurrencyIds)],
        'details' => ['nullable', 'array'],
        'details.*.product_id' => ['required', 'integer', 'exists:products,id', new UniqueInArray('product_id')],
        'details.*.product_collection_id' => ['nullable', 'integer', 'exists:product_collections,id'],
        ...
    ];
    

    Rule:

    <?php
    
    namespace AppRules;
    
    use IlluminateContractsValidationValidationRule;
    
    class UniqueInArray implements ValidationRule, DataAwareRule
    {
        /**
         * All of the data under validation.
         *
         * @var array<string, mixed>
         */
        protected $data = [];
    
        public function __invoke($attribute, $value, $fail): void
        {
            // This part is not safe but gives an idea to find the index
            $root = explode('.', $attribute)[0];
            $index = (int) explode('.', $attribute)[1];
            $key = explode('.', $attribute)[2];
    
            $duplicates = collect($this->data[$root] ?? [])->duplicates($key);
    
            if (array_key_exists($index, $duplicates)) {
                $fail($attribute, "Duplicate $key");
            }
        
        }
    
         /**
         * Set the data under validation.
         *
         * @param  array<string, mixed>  $data
         */
        public function setData(array $data): static
        {
            $this->data = $data;
    
            return $this;
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search