Othmane
Othmane

Reputation: 179

Laravel validation a field in an array

I'm building a farming app with Laravel and Vue. There are several activities that happen everyday in the farm and an activity might need to use products (fertilizers). In the Create.vue Activity page I'm generating multiple html inputs. A select to select a product an input to enter the quantity so I end up having a selected_products array in my request. Here's a data sample when I dd($request->all)

enter image description here

What is the problem?

The quantity entered in the selected_products array must be less than or equal to the quantity in stock. (can't have a product with id 9 with quantity 10 (liters or kilograms) when I only have 5 (liters or kilograms) in stock for that product)

So Product and Stock are one-to-one relationship

Product.php

public function stock () {
    return $this->morphOne(Stock::class, 'stockable');
}

(I'm using morphOne here 'cause stock can also include the fuel quantity for tractors just in case you're curious)

Anyways I just want the validation to fail and return an Out of stock if the quantity in stock for that very product is less than the quantity entered

By the way after the validation I'm doing this sort of thing in my ActivityController store method so I can attach date in the activity_product table

attach_product_data = [];
foreach ($request->input('selected_products') as $product) {
    $attach_product_data[$product['id']] = ['quantity' => $product['quantity']];
}

$activity->products()->attach($attach_product_data);

And this is what I'm trying to accomplish here but I have no idea how

StoreActivityRequest.php

public function rules()
{
    return [
        'selected_products.*.id' => ['nullable'],
        'selected_products.*.quantity' => ['required_with:selected_products.*.id',

    function () {
       /* first we should find the product with the 
       `selected_products.*.id` id and check if the quantity in stock is 
       greater than or equal to the quanity in 
       `selected_products.*.quantity` */
    }],
    ];
}

Upvotes: 0

Views: 906

Answers (1)

ThS
ThS

Reputation: 4783

After taking a look at some data sample, you might access the ID of each field in the selected_products array in a slightly different manner than your current approach.

The idea here is to no longer validate each selected_products.*.id and selected_products.*.quantity separately but rather use a validation callback on selected_products.* key in the rules array.

Here's a code sample to explain more:

public function rules()
{
    return [
        'selected_products' => ['array'], // Tell Laravel that we're expecting an array
        'selected_products.*' => [function ($attr, $val, $fail) {
            // $val is an array that contains the "id" and "quantity" keys
            // Only proceed validating when "id" is present (as i saw you're using "nullable" rule on "selected_products.*.id")
            if (isset($val['id'])) {
                // If no quantity is selected the raise a validation exception (based on your usage of "required_with" rule)
                !isset($val['quantity']) && $fail('the quantity is required when id is present.');
                // At this stage we're sure that both "id" and "quantity" are present and we might check the DB using the Product modal for example.
                // If no product is found using the current "id" then raise a validation failure exception
                !($prd = Product::whereId($val['id'])->first()) && fail('Unknown product with ID of ' . $val['id']);
                // If the quantity in the DB is less than the selected quantity then a validation failure exception is raised as well
                $prd->quantity < $val['quantity'] && fail('The selected quantity exceeds the product quantity in stock.');
            }
            // If no "id" is found on "selected_products.*" then nothing happens (based on your usage of "nullable" and "required_with" rules
        }],
    ];
}

Because i have spoke of the usage of the explode function above in the comments, here's a code sample that demonstrates it's usage which cannot be applied to your current data structure by the way so I'll improvise my own structure just to demonstrate.

To use the explode function, the fields should be indexed by the IDs (9, 8 and 1 as your data sample), something like this:

data sample

With that structure we have greatly reduced the size of the data being sent in the form as we have ditched the id key entirely along with quantity key and instead we simply have Product ID => Selected Quantity.

Based on that structure, we might validate the data as follows:

public function rules()
{
    return [
        'selected_products' => ['array'],
        'selected_products.*' => [function ($attr, $val, $fail) {
            // $val contains the selected quantity and $attr can help us get the respective "id"
            // We can use "explode" to get the "id"
            // Based on my above data sample, $attr can be "selected_products.9", "selected_products.8" or "selected_products.1"
            !($id = explode('.', $attr)[1] ?? null) && $fail('Unknown product id.');
            // Fetch the DB
            !($prd = Product::whereId($id) && fail('Unknown product with ID of ' . $val['id']);
            // Validate the selected quantity against what we have in the DB
            $prd->quantity < $val && fail('The selected quantity exceeds the product quantity in stock.');
        }],
    ];
}

In the end, I hope i have managed to land some help. Feel free to ask for more details/explanations at any time and meanwhile here are some useful links:

explode function documentation on php.net

Laravel docs about using closures (callback functions) as custom validation rules.

Upvotes: 1

Related Questions