In Laravel, what is a form request class, and when does it authorize and validate the request relative to your controller method?
answer
- a Request subclass you type-hint
- php artisan make:request, app/Http/Requests
- container afterResolving calls validateResolved()
- authorize() false means 403, before rules
- action body runs only after both pass
basics
~20 sA form request is a class extending Illuminate\Foundation\Http\FormRequest that carries authorize() and rules(). When the container resolves it for a type-hinted controller parameter, it authorizes and validates first; the action body runs only if both pass.
solid answer
~30 s`php artisan make:request StoreRegistrationRequest` generates a class in `app/Http/Requests` that extends `FormRequest`, with `authorize()` and `rules()`. You type-hint it on the controller action. When the container resolves that parameter, `FormRequestServiceProvider` registers an `afterResolving` hook that calls `validateResolved()`: it runs `prepareForValidation()`, then `authorize()` (a `false` throws `AuthorizationException`, rendered as a 403), then builds a validator from `rules()` and throws `ValidationException` if it fails. A browser submission is redirected back with the errors flashed; a client that expects JSON gets a 422. Only when everything passes does the method body run, with `$request->validated()` holding the checked data.
code
php · 22 lines<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreRegistrationRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user() !== null;
}
public function rules(): array
{
return [
'ticket_type' => ['required', 'in:standard,vip'],
'has_dietary_needs' => ['boolean'],
'dietary_notes' => ['required_if_accepted:has_dietary_needs', 'nullable', 'string', 'max:500'],
];
}
}go deeper
Recall the command, the directory, the two generated methods and that type-hinting the class is what triggers the check.
Explain the container hook that calls validateResolved() and the exact order: prepare, authorize, validate, passedValidation, then the action.
Show when a form request is worth it over inline validation, and why reading validated() instead of all() is the point of having run it.
Argue for a team convention: form requests for every write endpoint versus inline rules for trivial actions, weighing consistency against class count.
## What a form request is A **form request** is a class that extends `Illuminate\Foundation\Http\FormRequest`, which itself extends Laravel's ordinary `Illuminate\Http\Request`. So everything you can do with a request (`input()`, `user()`, `route()`, `boolean()`) still works inside it. What the subclass adds is a place to keep two decisions that would otherwise clutter a controller: - **`authorize()`** — may the current user perform this action at all? - **`rules()`** — what must the submitted data look like? You generate one with Artisan: ```bash php artisan make:request StoreRegistrationRequest ``` The class lands in `app/Http/Requests` (the directory is created if it does not exist), with both methods stubbed out. ## How it is triggered: the container, not the controller Nothing in your controller calls the validator. You **type-hint** the class on the action, and Laravel's service container builds it while resolving the action's parameters. The framework's `FormRequestServiceProvider` registers two container callbacks for this: 1. A `resolving` callback for `FormRequest` copies the current HTTP request's data into the new instance and gives it the container and the redirector. 2. An `afterResolving` callback for the `ValidatesWhenResolved` contract calls `validateResolved()` on it. `validateResolved()` then runs a fixed sequence: 1. `prepareForValidation()` — an empty hook you may override to normalise input. 2. `authorize()` — if it returns `false`, `failedAuthorization()` throws `AuthorizationException`. 3. The validator is built from `rules()` (plus `messages()`, `attributes()` and any `after()` callables). 4. If the validator fails, `failedValidation()` throws `ValidationException`. 5. `passedValidation()` — an empty hook that runs only on success. Because this happens during parameter resolution, the route's middleware has already run and route model binding has already replaced `{event}` with a model, but **not one line of the action body** has executed. ## What the client sees when it fails | Situation | Exception thrown | Default response | |---|---|---| | `authorize()` returns `false` | `AuthorizationException` | 403 ("This action is unauthorized.") | | rules fail, ordinary form post | `ValidationException` | redirect back, errors and old input flashed to the session | | rules fail, request expects JSON | `ValidationException` | 422 with the errors as JSON | Authorization is checked **before** the rules, so an unauthorized user gets a 403 and never learns which fields were wrong. The redirect target defaults to the previous URL; the form request can change it. ## Using it in the controller Inside the action you already know the data passed. The idiomatic next step is to read only the validated part: ```php public function store(StoreRegistrationRequest $request, Event $event) { $registration = $event->registrations()->create($request->validated()); return redirect()->route('events.show', $event); } ``` `validated()` returns only the keys that had rules, which is why it is preferred over `all()` for persistence. ## Why teams reach for it - **Thin controllers**: the action reads as "given valid input, do the work". - **Reuse**: the same `StoreRegistrationRequest` can serve a web controller and an API controller. - **Dependency injection in the hooks**: `rules()` and `authorize()` are called through the container, so they may type-hint services. - **One place for input preparation**: `prepareForValidation()`, `after()` and `passedValidation()` keep normalisation and cross-field checks next to the rules they support. - **Testability**: the rules are a plain method returning an array. For a one-field check in a small action, `$request->validate([...])` inline is fine; the form request earns its keep once the rules, the authorization question or the preparation logic grow. ## Common misunderstandings - The controller does **not** need to call `validate()` on a form request; resolving it already validated it. - A failed validation is an exception, not a return value, so no `if` in the controller is needed. - Instantiating the class yourself with `new StoreRegistrationRequest()` skips the container callbacks entirely, so no validation happens. - Route closures get the same treatment: a closure that type-hints the form request has its parameters resolved by the container, so the hook fires there too. - The rules run once per resolution. Calling `$request->validated()` several times in the action re-reads the stored result; it does not validate again. - A form request is a **separate object** built from the current request. It carries the same input, user and route, which is why `$request->user()` and `$request->route('event')` behave as they would on the plain request. ## A quick way to explain it in an interview "The controller declares what it needs — a valid, authorized registration — and the container refuses to hand it over until that is true." That framing covers the trigger (resolution), the order (authorize, then rules) and the outcome (exceptions, not return values) in one sentence.
- Does the controller's own code run if authorize() returns true but a rule fails?No. `validateResolved()` throws `ValidationException` while the container is still resolving the action's parameters, so the method body never starts. The exception handler turns it into a redirect back with flashed errors, or a 422 JSON response when the request expects JSON.
- Can rules() or authorize() receive services through their signatures?Yes. `FormRequest` calls both through `$this->container->call(...)`, so any type-hinted parameter is resolved from the container, for example a capacity service or a repository used to decide whether registration is still open.
A form request is the registration desk at a conference door: it checks your badge (authorize) and your filled-in form (rules) before you reach the hall, so the speaker inside never deals with a missing ticket.
saying these in an interview costs you the question
- The controller must call $request->validate() after type-hinting a form request
- Validation errors are returned to the controller to handle with an if
- Rules are checked before authorize(), so users see field errors first
- Creating the class with new runs the same validation
- A failed authorize() returns a 422 like any validation error