Laravel validation: quale approccio usare
Laravel validation su Laravel 13: quando usare validate(), Form Request o Validator, i nuovi attributi PHP e la risposta 422 per le API.
Laravel mette a disposizione tre modi per validare i dati in ingresso e servono a situazioni diverse: $request->validate() dentro il controller quando le regole sono poche, una Form Request quando crescono o vanno riusate, Validator::make() quando i dati non arrivano da una richiesta HTTP. Dalla versione 13, uscita il 17 marzo 2026, la configurazione delle Form Request si dichiara anche con attributi PHP sopra la classe, compresa una modalità strict che rifiuta i campi non previsti dalle regole.
Quali sono i tre modi di validare una richiesta?
I tre modi sono il metodo validate() sull'oggetto Request, la classe Form Request e la facade Validator, e producono lo stesso risultato con ergonomia diversa. Il primo vive dentro l'azione del controller.
public function store(Request $request)
{
$validati = $request->validate([
'titolo' => ['required', 'string', 'max:255'],
'corpo' => ['required'],
]);
Post::create($validati);
}
Se le regole falliscono Laravel lancia una Illuminate\Validation\ValidationException, che per una richiesta normale diventa un redirect alla pagina precedente con gli errori in sessione e i vecchi valori nel form. La facade Validator serve invece quando i dati non nascono da una richiesta HTTP, come le righe di un file importato o il payload di un job in coda: costruisci l'istanza con Validator::make($dati, $regole) e decidi tu cosa fare quando fails() restituisce vero, cosa utile anche per controllare un messaggio prima di consumarlo dentro un worker in coda.
Quando conviene passare a una Form Request?
Conviene quando la stessa validazione serve in più punti o le regole superano le cinque o sei righe, e ancora di più se insieme al controllo dei dati devi decidere se l'utente ha il permesso di procedere. La classe si genera con php artisan make:request StorePostRequest e finisce in app/Http/Requests.
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can('create', Post::class);
}
public function rules(): array
{
return [
'titolo' => ['required', 'unique:posts', 'max:255'],
'corpo' => ['required'],
];
}
}
Intorno a rules() ci sono i metodi che rendono la classe utile davvero. Con messages() cambi i testi degli errori e con attributes() il nome con cui il campo compare al loro interno, mentre prepareForValidation() sistema i dati prima del controllo, per esempio con $this->merge(['slug' => Str::slug($this->titolo)]). Il metodo after() aggiunge i controlli che dipendono dal database o da più campi insieme, e passedValidation() normalizza i dati quando è andato tutto bene. Nel controller dichiari la classe come parametro del metodo e la validazione avviene prima che il tuo codice parta.
Cosa aggiungono gli attributi PHP di Laravel 13?
Aggiungono un modo dichiarativo di configurare la Form Request senza sovrascrivere proprietà e metodi, e vivono tutti nel namespace Illuminate\Foundation\Http\Attributes. Ci sono #[StopOnFirstFailure] per fermare il controllo al primo errore, #[RedirectTo('/dashboard')] e #[RedirectToRoute('dashboard')] per cambiare la destinazione del redirect, #[ErrorBag('login')] per separare gli errori di form diversi nella stessa pagina, e #[FailOnUnknownFields] per la modalità strict.
use Illuminate\Foundation\Http\Attributes\FailOnUnknownFields;
#[FailOnUnknownFields]
class StorePostRequest extends FormRequest
{
public function rules(): array
{
return ['titolo' => ['required'], 'corpo' => ['required']];
}
}
La modalità strict è arrivata con Laravel 13.4.0 il 7 aprile 2026 e cambia un comportamento storico: fino a quel momento una Form Request validava i campi dichiarati e ignorava in silenzio tutti gli altri, quindi un client che spediva anche is_admin insieme a nome e email non riceveva nessun segnale, mentre adesso quella richiesta fallisce. Il valore predefinito resta disattivato e per accenderlo ovunque chiami FormRequest::failOnUnknownFields() dentro l'AppServiceProvider, ma su una applicazione già avviata conviene partire dalle rotte di scrittura più esposte, perché il cambio globale fa emergere di colpo tutti i campi extra che il frontend spedisce da anni.
Come si validano array e campi annidati?
Si validano con la notazione a punti, che descrive il percorso dentro la struttura come un indirizzo: autore.nome controlla il campo nome dentro l'oggetto autore, mentre tag.*.id applica la stessa regola a ogni elemento della lista tag.
$request->validate([
'autore.nome' => ['required', 'string'],
'tag' => ['array', 'max:5'],
'tag.*.id' => ['required', 'integer', 'exists:tags,id'],
]);
Per le regole che hanno bisogno di logica esiste la classe Rule, che evita di comprimere tutto dentro una stringa: Rule::exists('staff')->where(fn ($query) => $query->where('account_id', 1)) limita il controllo a un sottoinsieme di righe, Rule::enum(ServerStatus::class)->only([ServerStatus::Active]) accetta solo alcuni casi di un enum e Rule::date()->after(today()->addDays(7)) costruisce un vincolo temporale.
Cosa risponde Laravel quando la validazione fallisce su un'API?
Risponde con lo stato 422 Unprocessable Entity e un corpo JSON con il messaggio e l'elenco degli errori per campo, senza che tu debba scrivere niente. Il framework distingue il tipo di richiesta guardando l'header Accept, quindi lo stesso controller serve un redirect al browser e un JSON al client API.
{
"message": "Il campo titolo è obbligatorio.",
"errors": {
"titolo": ["Il campo titolo è obbligatorio."]
}
}
Chi lavora con Inertia riceve gli errori nella prop errors senza passare dal JSON grezzo, e il modo di mostrarli cambia parecchio tra Inertia con React e Livewire. I messaggi in italiano però non arrivano da soli, perché il framework spedisce solo le traduzioni inglesi.
Perché usare validated() invece dei dati grezzi?
Perché validated() restituisce soltanto i campi che hanno superato le regole, mentre $request->all() restituisce tutto quello che il client ha spedito, compreso ciò che non hai dichiarato. Passare i dati grezzi a create() o update() è il modo classico con cui un campo non previsto finisce nel database.
Il metodo safe() fa lo stesso lavoro ma restituisce un oggetto ValidatedInput, da cui chiami only(['nome', 'email']) per un sottoinsieme, except() per escludere campi e merge() per aggiungerne uno calcolato lato server. Insieme alla modalità strict copri il problema da entrambi i lati, perché validated() scarta i campi ignoti quando li usi e lo strict mode impedisce che arrivino.
Da dove partire su un progetto esistente
Parti convertendo in Form Request le due o tre rotte di scrittura più usate, quelle dove le regole sono già lunghe e ripetute tra store e update. Da lì aggiungi #[FailOnUnknownFields] su quelle stesse classi e guarda cosa si rompe nei test, perché il risultato dice esattamente quali campi extra il frontend sta spedendo. Il passaggio successivo è sostituire ogni $request->all() con validated() nelle scritture verso Eloquent.
La stessa disciplina vale anche fuori dalle rotte HTTP, per esempio negli strumenti esposti da un server MCP scritto in casa, dove l'input arriva da un modello linguistico e la validazione sta tra una chiamata sbagliata e una scrittura sul database. Il criterio di scelta resta quello di sempre: la Form Request quando le regole vivono più a lungo della singola azione.
Domande frequenti
Qual è la differenza tra validate() e una Form Request in Laravel?
Sono lo stesso motore di validazione con due punti di ingresso diversi. Il metodo validate() tiene le regole dentro il controller ed è comodo per due o tre campi, mentre la Form Request le sposta in una classe che puoi riusare, testare e arricchire con autorizzazione e messaggi personalizzati. Quando le regole compaiono in più di un posto la classe dedicata vince.
Come si personalizzano i messaggi di errore della validazione Laravel?
Dentro una Form Request definisci il metodo messages() con le chiavi nel formato campo.regola, per un singolo controllo passi l'array dei messaggi come secondo argomento di validate(), e per cambiare i testi in tutta l'applicazione modifichi il file lang/it/validation.php. Con attributes() invece cambi solo il nome con cui il campo compare nei messaggi.
Laravel valida i campi che non ho dichiarato nelle regole?
Storicamente il framework li ignorava in silenzio, senza segnalare niente. Da Laravel 13.4.0 puoi attivare la modalità strict con l'attributo #[FailOnUnknownFields] sulla Form Request oppure con FormRequest::failOnUnknownFields() per tutta l'applicazione, e a quel punto una richiesta che contiene campi non dichiarati fallisce la validazione invece di passare.
Che codice HTTP restituisce Laravel quando la validazione fallisce?
Restituisce 422 Unprocessable Entity alle richieste che si aspettano JSON, con un corpo che contiene la chiave message e la chiave errors con l'elenco dei problemi per ogni campo. Alle richieste dal browser risponde invece con un redirect alla pagina precedente, gli errori in sessione e i valori inseriti disponibili tramite old().
Grazie per aver letto fin qui.
Se l'articolo ti è stato utile, iscriviti alla newsletter per ricevere il prossimo direttamente in casella, oppure aggiungi il feed RSS al tuo lettore.