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
// 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], ]; }
Custom Rule: StrongPassword
php artisan make:rule StrongPassword
public function passes($attribute, $value): bool { return preg_match('/^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/', $value); }
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 } ] }
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', ]; }
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."); } } ] ]; }) ]; }
Reusable Custom Rule for arrays
php artisan make:rule HasSufficientStock
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.'; } }
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()); } } } ],
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']); }
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)