Reputation: 179
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)
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
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:
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