Validació bàsica
Com validar dades a Laravel amb el mètode validate(), regles integrades i gestió d'errors.
Com funciona la validació#
El flux de validació a Laravel segueix un patró senzill: reps les dades de la petició, les valides contra un conjunt de regles, i si passen la validació les processos. Si la validació falla, Laravel redirigeix automàticament l'usuari al formulari anterior amb els errors i els valors que havia introduït.
Validació al controlador#
La manera més directa de validar és amb el mètode validate() de l'objecte Request. Rep un array on les claus són els noms dels camps i els valors són les regles de validació:
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'body' => 'required|min:10',
'category' => 'required|in:tech,laravel,php',
'published_at' => 'nullable|date|after:today',
]);
// $validated conté NOMÉS els camps que han passat la validació
Article::create($validated);
return redirect()->route('articles.index')
->with('success', 'Article creat correctament!');
}El mètode validate() retorna un array amb només els camps validats. Si la validació falla, Laravel llança una excepció ValidationException que automàticament redirigeix l'usuari al formulari anterior amb els errors a la sessió. No cal gestionar manualment la redirecció en cas d'error.
Les regles es poden separar amb el caràcter | (pipe) o passar com un array, cosa que és més llegible quan hi ha moltes regles:
$validated = $request->validate([
'title' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'unique:users,email'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
'tags' => ['nullable', 'array'],
'tags.*' => ['string', 'max:50'],
]);La notació amb array és especialment necessària quan utilitzes regles que contenen comes o closures.
Regles de validació integrades#
Laravel inclou desenes de regles de validació que cobreixen la majoria de situacions. Aquí tens les més habituals organitzades per categoria:
Presència i tipus#
$request->validate([
'name' => 'required', // Camp obligatori
'bio' => 'nullable', // Pot ser null
'nickname' => 'sometimes', // Valida només si el camp és present
'title' => 'required|string', // Ha de ser text
'age' => 'required|integer', // Ha de ser un enter
'price' => 'required|numeric', // Ha de ser numèric (enters o decimals)
'active' => 'required|boolean', // Ha de ser true/false, 1/0, "1"/"0"
'data' => 'required|array', // Ha de ser un array
'config' => 'required|json', // Ha de ser JSON vàlid
]);Strings i longitud#
$request->validate([
'name' => 'required|string|min:2|max:100', // Entre 2 i 100 caràcters
'slug' => 'required|alpha_dash', // Lletres, números, guions
'code' => 'required|alpha_num', // Només lletres i números
'username' => 'required|regex:/^[a-z0-9_]+$/', // Regex personalitzat
'url' => 'required|url', // URL vàlida
'ip' => 'required|ip', // Adreça IP vàlida
'uuid' => 'required|uuid', // UUID vàlid
]);Números i rangs#
$request->validate([
'age' => 'required|integer|between:18,120', // Entre 18 i 120
'quantity' => 'required|integer|min:1|max:99', // Mínim 1, màxim 99
'price' => 'required|numeric|decimal:0,2', // Fins a 2 decimals
'rating' => 'required|integer|in:1,2,3,4,5', // Valors específics
'discount' => 'required|numeric|gt:0|lt:100', // Major que 0, menor que 100
]);Dates#
$request->validate([
'birthday' => 'required|date', // Data vàlida
'starts_at' => 'required|date|after:today', // Després d'avui
'ends_at' => 'required|date|after:starts_at', // Després de starts_at
'published_at' => 'nullable|date|before:tomorrow', // Abans de demà
'event_date' => 'required|date_format:Y-m-d', // Format específic
'meeting_at' => 'required|date|after_or_equal:today', // Avui o després
]);Base de dades#
use Illuminate\Validation\Rule;
$request->validate([
// Únic a la taula users, columna email
'email' => 'required|email|unique:users,email',
// Únic ignorant el registre actual (per a updates)
'email' => ['required', 'email', Rule::unique('users')->ignore($user->id)],
// Ha d'existir a la taula
'category_id' => 'required|exists:categories,id',
// Existeix amb condicions addicionals
'category_id' => ['required', Rule::exists('categories', 'id')->where('active', true)],
]);Fitxers#
$request->validate([
'avatar' => 'required|image|max:2048', // Imatge, màxim 2MB
'document' => 'required|file|mimes:pdf,doc,docx', // Tipus específics
'photo' => 'required|image|dimensions:min_width=100,min_height=100',
'attachment' => 'required|file|max:10240', // Qualsevol fitxer, 10MB
]);Confirmació i coincidència#
$request->validate([
// Requereix un camp password_confirmation que coincideixi
'password' => 'required|string|min:8|confirmed',
// Ha de coincidir amb la contrasenya actual
'current_password' => 'required|current_password',
// Igual a un altre camp
'email_repeat' => 'required|same:email',
// Diferent d'un altre camp
'new_password' => 'required|different:current_password',
]);Arrays i camps niuats#
Quan valides arrays o objectes niuats, utilitza la notació amb punts:
$request->validate([
'tags' => 'required|array|min:1|max:10',
'tags.*' => 'string|max:50',
'addresses' => 'required|array',
'addresses.*.street' => 'required|string',
'addresses.*.city' => 'required|string',
'addresses.*.zip' => 'required|string|size:5',
'settings.notifications' => 'required|boolean',
'settings.theme' => 'required|in:light,dark',
]);La notació * indica "cada element de l'array". Això permet validar cada adreça, cada etiqueta o cada element de qualsevol array.
Validar manualment#
De vegades necessites més control sobre el procés de validació. Pots crear un validador manualment amb la facade Validator:
use Illuminate\Support\Facades\Validator;
$validator = Validator::make($request->all(), [
'title' => 'required|max:255',
'body' => 'required',
]);
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput();
}
$validated = $validator->validated();Això és útil quan necessites executar lògica entre la validació i el processament, o quan vols gestionar els errors d'una manera diferent a la redirecció automàtica.
Afegir errors manualment#
Pots afegir errors al validador després de la validació inicial:
$validator = Validator::make($request->all(), [
'email' => 'required|email',
]);
$validator->after(function ($validator) use ($request) {
if ($this->hasTooManyAttempts($request)) {
$validator->errors()->add(
'email',
'Has superat el nombre màxim d\'intents. Torna a provar-ho més tard.'
);
}
});
if ($validator->fails()) {
// Gestionar errors
}Mostrar errors a Blade#
Quan la validació falla, Laravel redirigeix l'usuari al formulari anterior amb els errors disponibles a la variable $errors. Aquesta variable és una instància de MessageBag i està disponible automàticament a totes les vistes:
{{-- Mostrar tots els errors --}}
@if($errors->any())
<div class="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
<h4 class="text-red-800 font-semibold">Hi ha errors al formulari:</h4>
<ul class="mt-2 text-red-700 text-sm">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endifPer mostrar l'error d'un camp específic al costat del camp, utilitza la directiva @error:
<form method="POST" action="/articles">
@csrf
<div class="mb-4">
<label for="title">Títol</label>
<input
type="text"
name="title"
id="title"
value="{{ old('title') }}"
class="w-full border rounded px-3 py-2 @error('title') border-red-500 @enderror"
>
@error('title')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
<div class="mb-4">
<label for="body">Contingut</label>
<textarea
name="body"
id="body"
class="w-full border rounded px-3 py-2 @error('body') border-red-500 @enderror"
>{{ old('body') }}</textarea>
@error('body')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
<button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded">
Crear article
</button>
</form>La funció old() retorna el valor que l'usuari havia introduït al camp abans que la validació fallés. Això evita que l'usuari hagi de tornar a escriure tot el formulari.
Validació de peticions d'API#
Quan la petició espera una resposta JSON (peticions d'API, peticions AJAX), Laravel no redirigeix sinó que retorna una resposta JSON amb codi 422 i els errors estructurats:
{
"message": "The title field is required. (and 2 more errors)",
"errors": {
"title": ["The title field is required."],
"email": ["The email field must be a valid email address."],
"body": ["The body field must be at least 10 characters."]
}
}No cal fer res especial: Laravel detecta automàticament si la petició espera JSON (via la capçalera Accept: application/json) i ajusta la resposta.
Aturar a la primera fallada#
Per defecte, Laravel valida totes les regles de tots els camps i retorna tots els errors. Si vols que s'aturi a la primera regla que falla per a cada camp, utilitza bail:
$request->validate([
'title' => 'bail|required|string|max:255',
'email' => 'bail|required|email|unique:users',
]);Amb bail, si title no és required, no es comproven string ni max:255. Això pot ser útil per evitar missatges d'error redundants o per estalviar consultes a la base de dades.
Named Error Bags#
Quan tens múltiples formularis a la mateixa pàgina, pots separar els errors amb named error bags per evitar confusions:
$request->validateWithBag('login', [
'email' => 'required|email',
'password' => 'required',
]);A Blade, accedeix als errors del bag específic:
@error('email', 'login')
<p class="text-red-500">{{ $message }}</p>
@enderror
{{-- O comprovant el bag directament --}}
@if($errors->login->any())
{{-- Errors del formulari de login --}}
@endif