<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
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', 'unique:posts'],
'body' => ['required', 'string', 'min:100'],
'status' => ['required', Rule::in(['draft', 'published'])],
'published_at' => ['nullable', 'date', 'after:now'],
'tags' => ['array', 'max:5'],
'tags.*' => ['string', 'max:50'],
'featured_image' => ['nullable', 'image', 'max:2048'],
];
}
public function messages(): array
{
return [
'title.unique' => 'A post with this title already exists.',
'body.min' => 'Post content must be at least 100 characters.',
'tags.max' => 'You can add a maximum of 5 tags.',
];
}
public function attributes(): array
{
return [
'published_at' => 'publication date',
'featured_image' => 'image',
];
}
protected function prepareForValidation(): void
{
$this->merge([
'slug' => str($this->title)->slug(),
]);
}
}
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StorePostRequest;
use App\Http\Requests\UpdatePostRequest;
use App\Models\Post;
class PostController extends Controller
{
public function store(StorePostRequest $request)
{
// Validation already passed, authorization already checked
$post = Post::create($request->validated());
if ($request->hasFile('featured_image')) {
$post->addMedia($request->file('featured_image'))
->toMediaCollection('featured');
}
return redirect()
->route('posts.show', $post)
->with('success', 'Post created successfully!');
}
public function update(UpdatePostRequest $request, Post $post)
{
$post->update($request->validated());
return redirect()
->route('posts.show', $post)
->with('success', 'Post updated successfully!');
}
}
Form requests encapsulate validation logic in dedicated classes, keeping controllers thin and focused. Each form request extends FormRequest and defines rules() and optionally authorize() methods. The authorize() method checks if the user can perform the action, returning a 403 if false. Custom error messages go in the messages() method, while attribute names for error display use attributes(). Form requests support conditional validation—rules that apply only when certain conditions are met. The validated() method returns only validated data, preventing mass assignment vulnerabilities. Failed validation automatically redirects back with errors in session. I create separate requests for create and update operations since validation rules often differ.
Related snips
class SignupForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :account_name, :string
attribute :email, :string
Shallow Controller, Deep Params: Form Object Pattern
<?php
namespace App\Providers;
use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;
Laravel service container and dependency injection
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Form Validation Example</title>
<style>
HTML forms with validation and accessibility
{
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build"
},
Laravel mix/Vite for asset compilation
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Laravel database migrations for schema management
import axios from 'axios';
export type NormalizedErrors = {
fields: Record<string, string>;
formLevel: string | null;
};
Frontend: normalize and display server validation errors
Share this code
Here's the card — post it anywhere.