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)