Appearance
Meeting 16 - Request Handling & Validation
Goal: Understand Laravel's HTTP request lifecycle, how dependencies are injected, how validation works (Form Requests, manual Validator, custom rules), route model binding, file uploads, conditional validation, and best practices. (API-specific validation & JSON error formats will be covered separately in the next meeting.)
1. Lifecycle (High Level)
- HTTP request enters
public/index.php. - Framework bootstraps service providers.
- HTTP Kernel runs global middleware (maintenance mode, trim strings, trust proxies, etc.).
- Router matches route & its middleware stack.
- Route parameters resolved (including implicit route model binding).
- Controller / Closure executed (dependencies injected via container).
- Response returned -> middleware (after) -> sent to client.
Diagram:
2. Route Definitions & Binding
routes/web.php:
php
Route::get('/posts/{post}', [PostController::class,'show']);Route::get('/posts/{post}', [PostController::class,'show']);Implicit model binding: {post} maps to App\Models\Post by naming convention. Explicit model binding (RouteServiceProvider):
php
public function boot(): void
{
parent::boot();
Route::model('post', Post::class); // optional explicit
}public function boot(): void
{
parent::boot();
Route::model('post', Post::class); // optional explicit
}Custom key:
php
public function getRouteKeyName(): string { return 'slug'; }
// Now /posts/{post:slug}public function getRouteKeyName(): string { return 'slug'; }
// Now /posts/{post:slug}3. Dependency Injection
Controller method:
php
public function store(StorePostRequest $request, MarkdownParser $parser)
{
$html = $parser->toHtml($request->input('body'));
Post::create($request->validated()+['body_html'=>$html,'user_id'=>$request->user()->id]);
return redirect()->route('posts.index');
}public function store(StorePostRequest $request, MarkdownParser $parser)
{
$html = $parser->toHtml($request->input('body'));
Post::create($request->validated()+['body_html'=>$html,'user_id'=>$request->user()->id]);
return redirect()->route('posts.index');
}MarkdownParser auto-resolved from container if bound (app()->bind(MarkdownParser::class,...)).
4. Validation Options
| Method | When to Use | Pros | Cons |
|---|---|---|---|
| Form Request | Reusable complex validation & authorization | Clean controllers, testable | Extra class |
Validator::make() | Dynamic / conditional rules inside method | Flexible | More code in controller |
Inline validate() | Very small forms | Fast & concise | Grows messy for complex logic |
5. Form Request Example
bash
php artisan make:request StorePostRequestphp artisan make:request StorePostRequestapp/Http/Requests/StorePostRequest.php:
php
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{ return $this->user() != null; }
public function rules(): array
{
return [
'title' => ['required','string','min:3','max:150'],
'body' => ['required','string','min:20'],
'tags' => ['array','max:5'],
'tags.*'=> ['string','distinct'],
'cover' => ['nullable','image','max:2048'], // KB
'published_at' => ['nullable','date','after:now'],
];
}
public function messages(): array
{
return [ 'title.required' => 'Title cannot be empty.' ];
}
protected function prepareForValidation(): void
{
$this->merge(['title' => trim($this->title)]);
}
}class StorePostRequest extends FormRequest
{
public function authorize(): bool
{ return $this->user() != null; }
public function rules(): array
{
return [
'title' => ['required','string','min:3','max:150'],
'body' => ['required','string','min:20'],
'tags' => ['array','max:5'],
'tags.*'=> ['string','distinct'],
'cover' => ['nullable','image','max:2048'], // KB
'published_at' => ['nullable','date','after:now'],
];
}
public function messages(): array
{
return [ 'title.required' => 'Title cannot be empty.' ];
}
protected function prepareForValidation(): void
{
$this->merge(['title' => trim($this->title)]);
}
}Usage in controller: parameter type-hint triggers automatic validation; errors redirect back with old input.
6. Manual Validator
php
use Illuminate\Support\Facades\Validator;
$validator = Validator::make($data, [
'email' => ['required','email'],
'age' => ['required','integer','min:18'],
]);
if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}use Illuminate\Support\Facades\Validator;
$validator = Validator::make($data, [
'email' => ['required','email'],
'age' => ['required','integer','min:18'],
]);
if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}Get validated data: $clean = $validator->validated();
7. Conditional Validation
php
Validator::make($data, [
'type' => ['required','in:free,premium'],
'company' => [Rule::requiredIf($data['type'] === 'premium')],
]);Validator::make($data, [
'type' => ['required','in:free,premium'],
'company' => [Rule::requiredIf($data['type'] === 'premium')],
]);Or using sometimes:
php
$validator->sometimes('company','required', fn($input) => $input->type === 'premium');$validator->sometimes('company','required', fn($input) => $input->type === 'premium');8. Custom Rule Class
bash
php artisan make:rule UppercaseWordsphp artisan make:rule UppercaseWordsapp/Rules/UppercaseWords.php:
php
class UppercaseWords implements Rule
{
public function passes($attribute, $value): bool
{ return collect(explode(' ', $value))->every(fn($w) => strtoupper($w) === $w); }
public function message(): string
{ return 'Each word in :attribute must be uppercase.'; }
}class UppercaseWords implements Rule
{
public function passes($attribute, $value): bool
{ return collect(explode(' ', $value))->every(fn($w) => strtoupper($w) === $w); }
public function message(): string
{ return 'Each word in :attribute must be uppercase.'; }
}Use in rules: 'title' => ['required', new UppercaseWords].
9. File Upload Validation & Storage
Form:
blade
<form method="POST" action="/posts" enctype="multipart/form-data">
@csrf
<input type="file" name="cover" />
</form><form method="POST" action="/posts" enctype="multipart/form-data">
@csrf
<input type="file" name="cover" />
</form>Handling:
php
if ($request->hasFile('cover')) {
$path = $request->file('cover')->store('covers','public');
}if ($request->hasFile('cover')) {
$path = $request->file('cover')->store('covers','public');
}Serve files via storage:link.
10. Array & Nested Data
Rules for nested arrays:
php
'items' => ['required','array','max:10'],
'items.*.name' => ['required','string'],
'items.*.qty' => ['required','integer','min:1'],'items' => ['required','array','max:10'],
'items.*.name' => ['required','string'],
'items.*.qty' => ['required','integer','min:1'],Validate unique combinations using distinct or custom closure rule.
11. Sanitization & Data Transformation
Use prepareForValidation() or separate DTO / Action class. Keep validation (rules) distinct from transformation (formatting, trimming, parsing Markdown).
12. Rate Limiting on Validation Endpoints
Too many invalid attempts? Apply throttle:
php
Route::post('/login', LoginController::class)->middleware('throttle:login');Route::post('/login', LoginController::class)->middleware('throttle:login');Define limiter in RouteServiceProvider.
13. Authorization + Validation
Combine inside Form Request:
php
public function authorize(): bool
{ return $this->user()?->can('create', Post::class) ?? false; }public function authorize(): bool
{ return $this->user()?->can('create', Post::class) ?? false; }If authorize() returns false, a 403 response is generated automatically.
14. Handling Validation in Services
Prefer injecting already-validated data (from Form Request) into service layer:
php
class CreatePostAction
{
public function __invoke(array $data): Post
{ return Post::create($data); }
}class CreatePostAction
{
public function __invoke(array $data): Post
{ return Post::create($data); }
}Controller:
php
public function store(StorePostRequest $request, CreatePostAction $action)
{ $post = $action($request->validated()); }public function store(StorePostRequest $request, CreatePostAction $action)
{ $post = $action($request->validated()); }15. Common Rule Reference
| Rule | Purpose | Example |
|---|---|---|
required | Field must be present & not empty | 'title' => 'required' |
sometimes | Apply rules conditionally | $validator->sometimes(...) |
exists:table,column | Must reference existing row | user_id foreign key |
unique:table,column | Value must be unique | unique email |
after:date | Must be after another field/date | published_at future |
confirmed | Requires field_confirmation match | password confirmation |
prohibited_unless | Disallow field unless condition true | advanced toggles |
nullable | Allows null (skip other rules if null) | optional fields |
regex:/^...$/ | Match pattern | SKU formatting |
16. Best Practices
- Keep controllers thin: push heavy validation logic to Form Requests.
- Do not trust client-sent IDs for ownership; re-check authorization.
- Use custom rule classes for complex / reusable constraints.
- Validate arrays & nested structures explicitly (
items.*.id). - For large payloads, fail fast & return concise errors.
- Sanitize (trim, normalize casing) before validation when needed.
- Distinguish between validation errors (422) and authorization errors (403).
- Log validation anomaly spikes (possible abuse).
17. Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Rules not applied | Wrong Form Request namespace | Check controller import |
| File size always fails | Missing enctype | Add multipart/form-data |
| Getting redirect instead of errors | Missing required route names or middleware order | Ensure correct form method & route; API JSON behavior in Meeting 17 |
| Custom rule not triggered | Not instantiated | Use new CustomRule |
unique fails on update | No ID ignore | Rule::unique('posts','slug')->ignore($post->id) |
18. Summary
The request pipeline ensures data arrives sanitized, authorized, and valid before business logic runs. Use Form Requests for consistency, combine with policies for security, and keep transformation separate from validation for clarity. API-specific validation responses (JSON 422 format, error shaping, pagination meta) will be discussed next.
Next: API Validation & Error Formatting + Testing Strategies (Meeting 17).