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.
