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)