Skip to content

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)

  1. HTTP request enters public/index.php.
  2. Framework bootstraps service providers.
  3. HTTP Kernel runs global middleware (maintenance mode, trim strings, trust proxies, etc.).
  4. Router matches route & its middleware stack.
  5. Route parameters resolved (including implicit route model binding).
  6. Controller / Closure executed (dependencies injected via container).
  7. Response returned -> middleware (after) -> sent to client.

Diagram: Request Validation Flow

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

MethodWhen to UseProsCons
Form RequestReusable complex validation & authorizationClean controllers, testableExtra class
Validator::make()Dynamic / conditional rules inside methodFlexibleMore code in controller
Inline validate()Very small formsFast & conciseGrows messy for complex logic

5. Form Request Example

bash
php artisan make:request StorePostRequest
php artisan make:request StorePostRequest

app/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 UppercaseWords
php artisan make:rule UppercaseWords

app/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

RulePurposeExample
requiredField must be present & not empty'title' => 'required'
sometimesApply rules conditionally$validator->sometimes(...)
exists:table,columnMust reference existing rowuser_id foreign key
unique:table,columnValue must be uniqueunique email
after:dateMust be after another field/datepublished_at future
confirmedRequires field_confirmation matchpassword confirmation
prohibited_unlessDisallow field unless condition trueadvanced toggles
nullableAllows null (skip other rules if null)optional fields
regex:/^...$/Match patternSKU 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

SymptomCauseFix
Rules not appliedWrong Form Request namespaceCheck controller import
File size always failsMissing enctypeAdd multipart/form-data
Getting redirect instead of errorsMissing required route names or middleware orderEnsure correct form method & route; API JSON behavior in Meeting 17
Custom rule not triggeredNot instantiatedUse new CustomRule
unique fails on updateNo ID ignoreRule::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).