Laravel Validation & Form Requests

·

·

Validation protects your application from bad data before it reaches the database or business logic. Laravel provides dozens of built-in rules and extension points for domain-specific constraints.

Validation Rules and Messages

Call $request->validate([…]) inline or move rules to Form Request classes for reuse across web and API controllers.

Rules can be strings, Rule objects, or arrays combining multiple constraints per field.

Step 1: Common field rules

'email' => 'required|email:rfc,dns|max:255' validates format and length. 'avatar' => 'nullable|image|max:2048' handles optional uploads.

Use distinct for arrays of IDs and exists:posts,id to ensure foreign keys reference real rows.

'slug' => 'required|alpha_dash|unique:posts,slug',

Step 2: Rule objects for clarity

Rule::unique('users')->ignore($user->id) updates profiles without conflicting on the same email. Rule::in(['draft', 'published']) replaces pipe strings.

Rule::password()->min(12)->mixedCase() configures defaults reused in registration and password change forms.

use IlluminateValidationRule;
'email' => ['required', Rule::unique('users')->ignore($id)],

Step 3: Custom error messages

Pass second array to validate() mapping 'email.required' => 'We need your email.' Form requests implement messages() and attributes() for friendly names.

Translate messages with __('validation.custom.email.required') entries in lang files for multilingual apps.

public function messages(): array
{
    return ['title.required' => 'Add a headline.'];
}

Step 4: Conditional rules

sometimes|required applies when field is present. required_if:status,published makes body required only for published posts.

Rule::requiredIf(fn () => request('type') === 'company') handles complex dependencies readable in PHP.

'company_name' => 'required_if:type,company',

Form Requests in Depth

Form requests extend IlluminateFoundationHttpFormRequest with authorize(), rules(), and optional prepareForValidation().

Failed authorization returns 403; failed validation returns 422 JSON or redirect with errors.

Step 1: authorize method

return $this->user()->can('update', $this->route('post')); ties validation entry to policies before rules run.

Returning false triggers 403 automatically. Gate authorization exceptions include messages when using Response::deny.

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

Step 2: prepareForValidation

Merge transformed input before rules execute: $this->merge(['slug' => Str::slug($this->title)]); normalizes data early.

Use prepareForValidation to cast query string booleans or strip formatting from phone numbers.

protected function prepareForValidation(): void
{
    $this->merge(['slug' => Str::slug($this->slug)]);
}

Step 3: after validation hooks

withValidator($validator) adds closures after rules register. $validator->after(function ($v) { if (…) $v->errors()->add('field', 'msg'); });

Cross-field validation—end date after start date—fits after hooks better than awkward rule strings.

$validator->after(function ($validator) {
    if ($this->ends_at <= $this->starts_at) {
        $validator->errors()->add('ends_at', 'Must be after start.');
    }
});

Step 4: Validated and safe input

$request->validated() returns only keys mentioned in rules(), preventing mass assignment surprises.

Use safe() when you need validated data plus optional fields excluded from rules intentionally.

$data = $request->safe()->merge(['user_id' => auth()->id()]);

Custom Rules and API Errors

php artisan make:rule ValidTaxId generates invokable or object rules encapsulating reusable validation logic.

API clients expect consistent JSON error shapes; customize failedValidation in form requests when needed.

Step 1: Invokable custom rules

public function validate(string $attribute, mixed $value, Closure $fail): void { if (! $this->isValid($value)) $fail('Invalid tax ID'); }

Register inline with new ValidTaxId or string alias in service provider for 'tax_id' => ['required', 'tax_id'].

public function validate(string $attribute, mixed $value, Closure $fail): void { ... }

Step 2: Rule objects with dependencies

Inject services in custom rule constructors for database lookups or external API verification during validation.

Keep IO in rules minimal; queue heavy checks when UX allows async confirmation.

public function __construct(private TaxService $tax) {}

Step 3: JSON validation responses

failedValidation throws ValidationException with 422 and errors keyed by field. Customize format for Problem Details consumers.

In APIs, return error codes machines can parse: 'code' => 'EMAIL_TAKEN' alongside human messages.

throw new HttpResponseException(response()->json(['errors' => $validator->errors()], 422));

Step 4: Validate arrays and nested data

'items.*.quantity' => 'required|integer|min:1' validates each cart line. use IlluminateSupportArr for dot notation error display.

Form requests can define rules() dynamically based on cart item count from route parameters.

'items' => 'array|min:1',
'items.*.sku' => 'required|exists:products,sku',

Aadyanex centralizes Laravel validation in form requests and shared rule objects so APIs and admin panels enforce the same business constraints.