DEV Community

Cover image for Laravel Form Requests + Custom Rules: Powerful, Scalable, and Smart Validation
Aleson França
Aleson França

Posted on

Laravel Form Requests + Custom Rules: Powerful, Scalable, and Smart Validation

Still validating data directly in your controller? Let’s level up your Laravel skills with:

  • Form Request for clean validation
  • Reusable Custom Rules
  • Smart array validation with advanced logic

Let me show you real-world examples.


Using Form Requests for cleaner code

Generate a request:

php artisan make:request StoreUserRequest 
Enter fullscreen mode Exit fullscreen mode
// app/Http/Requests/StoreUserRequest.php public function rules(): array { return [ 'name' => 'required|string|min:3', 'email' => 'required|email|unique:users,email', 'password' => ['required', 'min:8', new StrongPassword], ]; } 
Enter fullscreen mode Exit fullscreen mode

Custom Rule: StrongPassword

php artisan make:rule StrongPassword 
Enter fullscreen mode Exit fullscreen mode
public function passes($attribute, $value): bool { return preg_match('/^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/', $value); } 
Enter fullscreen mode Exit fullscreen mode

This rule checks for:

  • At least 1 uppercase letter
  • At least 1 number
  • At least 1 special character

Validating arrays of objects: Order items examples

POST /api/orders { "customer_id": 1, "items": [ { "product_id": 101, "quantity": 2 }, { "product_id": 202, "quantity": 5 } ] } 
Enter fullscreen mode Exit fullscreen mode
public function rules(): array { return [ 'customer_id' => 'required|exists:customers,id', 'items' => 'required|array|min:1', 'items.*.product_id' => 'required|exists:products,id', 'items.*.quantity' => 'required|integer|min:1', ]; } 
Enter fullscreen mode Exit fullscreen mode

Custom validation for each item in array

Let’s check if the quantity is not greater than the product stock using Rule::forEach.

use Illuminate\Validation\Rule; public function rules(): array { return [ 'items' => ['required', 'array', 'min:1'], 'items.*' => Rule::forEach(function ($item, $index) { return [ 'product_id' => ['required', Rule::exists('products', 'id')], 'quantity' => [ 'required', 'integer', 'min:1', function ($attribute, $value, $fail) use ($item) { $product = \App\Models\Product::find($item['product_id']); if ($product && $value > $product->stock) { $fail("The quantity in {$attribute} exceeds available stock."); } } ] ]; }) ]; } 
Enter fullscreen mode Exit fullscreen mode

Reusable Custom Rule for arrays

php artisan make:rule HasSufficientStock 
Enter fullscreen mode Exit fullscreen mode
class HasSufficientStock implements Rule { public function __construct(public int $productId) {} public function passes($attribute, $value): bool { $product = Product::find($this->productId); return $product && $value <= $product->stock; } public function message(): string { return 'Quantity exceeds available stock.'; } } 
Enter fullscreen mode Exit fullscreen mode

Usage:

'items.*.quantity' => [ 'required', 'integer', function ($attribute, $value, $fail) use ($request, $index) { $productId = $request->input("items.$index.product_id"); if ($productId) { $rule = new HasSufficientStock($productId); if (!$rule->passes($attribute, $value)) { $fail($rule->message()); } } } ], 
Enter fullscreen mode Exit fullscreen mode

Testing validation with arrays

public function test_fails_if_quantity_exceeds_stock(): void { Product::factory()->create(['id' => 1, 'stock' => 5]); $response = $this->postJson('/api/orders', [ 'customer_id' => 1, 'items' => [ ['product_id' => 1, 'quantity' => 10] ] ]); $response->assertStatus(422); $response->assertJsonValidationErrors(['items.0.quantity']); } 
Enter fullscreen mode Exit fullscreen mode

Conclusion

Using Form Requests, Custom Rules, and smart array validation gives you:

  • Clean and maintainable code

  • Reusable validation logic

  • Better test coverage

  • Safer and more predictable APIs

Have you used Rule::forEach in your Laravel project or some other kind of validation or custom rules? Or are you still validating everything inside your controller ?

Top comments (0)