What if your customer is filling in the order form, and meanwhile the product price has changed? Or, some product becomes out of stock? We need to re-validate the quantities/prices after the submit, right? In this article, I will show you two ways: regular Laravel and more UX-friendly "live validation" with Livewire.
Look at the validation messages above: this is exactly what we will be building. After the submit, the price of Product 1 changed in the DB, and also someone bought Product 2 so there are not enough items left.
It's a pretty complex validation, there's no ready-made Laravel validation rule in the official list. We need to create a Custom Validation Rule. But first, the initial project structure.
Initial Structure
The database structure is simple - just one "products" table:
I've created a simple demo project with the design based on our Laravel Breeze Skeleton, two Routes, and one Controller.
routes/web.php:
1Route::get('orders/create', [OrderController::class, 'create'])->name('orders.create');2Route::post('orders', [OrderController::class, 'store'])->name('orders.store');
app/Http/Controllers/OrderController.php:
1use App\Models\Product; 2 3class OrderController extends Controller 4{ 5 public function create() 6 { 7 $products = Product::all(); 8 9 return view('orders.create', compact('products'));10 }11}
The form in Laravel Blade looks like this:
resources/views/orders/create.blade.php:
1// ... here are the main layout tags - skipping them in this snippet 2 3<x-validation-errors class="mb-4" :errors="$errors" /> 4 5<form action="{{ route('orders.store') }}" method="POST"> 6 @csrf 7 <table> 8 <thead> 9 <tr>10 <th>Product</th>11 <th>Price</th>12 <th>Quantity</th>13 </tr>14 </thead>15 16 <tbody>17 @foreach($products as $product)18 <input type="hidden"19 name="prices[{{ $product->id }}]"20 value="{{ $product->price }}" />21 22 <tr>23 <td>{{ $product->name }}</td>24 <td>${{ number_format($product->price, 2) }}</td>25 <td>26 <input type="number"27 name="products[{{ $product->id }}]"28 value="{{ old('products.' . $product->id, 0) }}" />29 </td>30 </tr>31 @endforeach32 </tbody>33 </table>34 <x-button>Place Order</x-button>35</form>
Notice: I intentionally deleted CSS classes and layout HTML tags here, to focus on the form functionality, you can find the original code with all classes in the Github repository.
As you can see, we pass two array parameters from the form:
-
products[]
asID => quantity
array -
prices[]
asID => price
array in thehidden
field
Now, the submission of the form goes to the store()
method of the Controller, where we get to the main question:
app/Http/Controllers/OrderController.php:
1use Illuminate\Http\Request; 2 3class OrderController extends Controller 4{ 5 // ... 6 7 public function store(Request $request) 8 { 9 $request->validate([10 'products' => '????', // <- WHAT TO PUT HERE?11 ]);12 13 // ... Create the order and more14 }15}
Custom Validation Rule
We need to validate two things for each of the products:
- Has the price changed?
- Do we have enough items in stock?
As I mentioned above, there's no available rule for this in Laravel, so we need to make our own.
1php artisan make:rule ProductStockPriceRule --invokable
I will use the new "invokable" syntax of validation rules that appeared in Laravel 9, you can read about it in the official docs.
The initial generated structure...