$ lexprog.com

// notes from an old coder -- php, databases, and the occasional rant

[April 02, 2025] Laravel

Laravel Validation Deep Dive

Laravel Validation: Tips & Tricks

────────────────────────────────────────────────────────

Laravel Validation: Tips & Tricks

Tip: Use Form Request Classes

php artisan make:request StorePostRequest

Keeps validation out of controllers:

class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', Post::class);
    }

    public function rules(): array
    {
        return [
            'title' => 'required|string|max:255',
            'slug' => 'required|unique:posts,slug',
        ];
    }
}

Gotcha: unique Rule Ignores Soft Deletes

'slug' => 'unique:posts,slug,NULL,id,deleted_at,NULL'

The last two parameters exclude soft-deleted records from the uniqueness check.

Tip: Use sometimes() for Conditional Validation

$validator->sometimes('reason', 'required', function ($input) {
    return $input->status === 'rejected';
});

Tip: Custom Validation Messages

public function messages(): array
{
    return [
        'title.required' => 'The title field is absolutely required.',
        'email.unique' => 'This email is already registered.',
    ];
}

Gotcha: Array Validation Syntax

'tags.*' => 'required|string|max:50',

Validates each item in the tags array. Don't forget the .*.

Tip: Use bail to Stop on First Failure

'title' => 'bail|required|string|max:255',

Without bail, all rules run even if required fails.

Tip: Validate Nested Data

$request->validate([
    'address.street' => 'required',
    'address.city' => 'required',
    'address.zip' => 'required|regex:/^\d{5}$/',
]);

Gotcha: confirmed Rule Needs a Matching Field

password + password_confirmation. The rule looks for {field}_confirmation.

Tip: Use route:cache Carefully

php artisan route:cache is fast, but it doesn't work with closure-based routes. Every time you cache routes, Laravel serializes them. If you have Route::redirect() or closure callbacks, the cache breaks. Stick to controller-based routes in production.

Tip: Model APP_KEY Rotation

Rotating APP_KEY invalidates all encrypted data — cookies, encrypted DB columns, and password reset tokens. If you must rotate (e.g., after a leak), plan a migration that re-encrypts existing data with the new key.

Gotcha: Local Scope Leaks

Global scopes defined in booted() apply to ALL queries on that model — including relationships. An innocent User::all() in admin panel might exclude soft-deleted users if a global scope is active.

Senior Insight

Form Requests are one of Laravel's best features, but they're also a common source of hidden complexity. The issue I encounter most: authorization logic inside Form Requests that duplicates policy logic elsewhere, creating two places to update when permissions change. My practice is to keep Form Requests focused on validation rules only, and handle authorization through the controller or middleware. One concern per class — it's simple but it works.

Source: Laravel News (https://laravel-news.com/), Freek.dev (https://freek.dev/tags/laravel), Spatie Blog (https://spatie.be/blog)

────────────────────────────────────────────────────────
<-- back to posts