Missatges d'error
Com personalitzar missatges d'error de validació, localitzar-los i mostrar-los als formularis.
Com funcionen els missatges d'error#
Quan la validació falla, Laravel genera un missatge d'error per cada regla que no s'ha complert. Per defecte, els missatges són en anglès i segueixen el format "The field is required" o "The must be at least characters". Pots personalitzar-los a diversos nivells: per regla concreta, per camp, globalment al fitxer de localització, o directament al Form Request.
Missatges inline#
La manera més directa de personalitzar missatges és passar-los com a segon paràmetre del mètode validate():
$request->validate([
'title' => 'required|max:255',
'email' => 'required|email|unique:users',
'body' => 'required|min:10',
], [
'title.required' => 'Has d\'escriure un títol per a l\'article.',
'title.max' => 'El títol és massa llarg. Màxim :max caràcters.',
'email.required' => 'Necessitem el teu correu electrònic.',
'email.email' => 'El format del correu no és vàlid.',
'email.unique' => 'Aquest correu ja està registrat.',
'body.required' => 'L\'article necessita contingut.',
'body.min' => 'El contingut és massa curt. Escriu almenys :min caràcters.',
]);El format és camp.regla. Laravel substitueix automàticament els placeholders com :max, :min, :attribute i d'altres pels valors corresponents.
Localització#
Per a projectes multilingües o per tenir un sol fitxer central amb tots els missatges, utilitza els fitxers de localització. Crea el fitxer lang/ca/validation.php per tenir els missatges en català:
// lang/ca/validation.php
return [
'accepted' => 'El camp :attribute ha de ser acceptat.',
'between' => [
'numeric' => 'El camp :attribute ha d\'estar entre :min i :max.',
'string' => 'El camp :attribute ha de tenir entre :min i :max caràcters.',
],
'confirmed' => 'La confirmació del camp :attribute no coincideix.',
'email' => 'El camp :attribute ha de ser una adreça de correu vàlida.',
'exists' => 'El :attribute seleccionat no existeix.',
'image' => 'El camp :attribute ha de ser una imatge.',
'in' => 'El :attribute seleccionat no és vàlid.',
'max' => [
'numeric' => 'El camp :attribute no pot ser superior a :max.',
'string' => 'El camp :attribute no pot superar els :max caràcters.',
'file' => 'El camp :attribute no pot pesar més de :max kilobytes.',
],
'min' => [
'numeric' => 'El camp :attribute ha de ser com a mínim :min.',
'string' => 'El camp :attribute ha de tenir almenys :min caràcters.',
'file' => 'El camp :attribute ha de pesar almenys :min kilobytes.',
],
'numeric' => 'El camp :attribute ha de ser un número.',
'required' => 'El camp :attribute és obligatori.',
'string' => 'El camp :attribute ha de ser text.',
'unique' => 'El :attribute ja existeix.',
'url' => 'El camp :attribute ha de ser una URL vàlida.',
];Personalitzar noms d'atributs#
Per defecte, Laravel utilitza el nom del camp als missatges (per exemple, "El camp email és obligatori"). Pots donar-los noms més llegibles a l'array attributes del mateix fitxer:
// lang/ca/validation.php
return [
// ... regles ...
'attributes' => [
'name' => 'nom',
'email' => 'correu electrònic',
'password' => 'contrasenya',
'password_confirmation' => 'confirmació de la contrasenya',
'title' => 'títol',
'body' => 'contingut',
'phone' => 'telèfon',
'address' => 'adreça',
'city' => 'ciutat',
'country' => 'país',
'category_id' => 'categoria',
'published_at' => 'data de publicació',
],
];Ara els missatges dirien "El camp títol és obligatori" o "El correu electrònic ha de ser una adreça de correu vàlida".
Missatges personalitzats per camp al fitxer de localització#
Pots definir missatges específics per camp dins del fitxer de localització, a l'array custom:
// lang/ca/validation.php
return [
// ... regles generals ...
'custom' => [
'email' => [
'required' => 'Necessitem el teu correu per poder contactar-te.',
'unique' => 'Ja tenim un compte amb aquest correu.',
],
'password' => [
'min' => 'La contrasenya ha de tenir com a mínim :min caràcters per seguretat.',
],
],
];Configurar l'idioma#
Per utilitzar el fitxer de localització en català, configura l'idioma al fitxer config/app.php:
'locale' => 'ca',
'fallback_locale' => 'en',O al fitxer .env:
APP_LOCALE=ca
APP_FALLBACK_LOCALE=enTreballar amb l'objecte d'errors#
Quan la validació falla, els errors s'emmagatzemen a la sessió i estan disponibles a les vistes a través de la variable $errors, que és una instància de Illuminate\Support\MessageBag:
// Comprovar si hi ha errors
$errors->any(); // true si hi ha algun error
$errors->isEmpty(); // true si no hi ha errors
// Obtenir errors
$errors->all(); // Array amb tots els missatges
$errors->first('title'); // Primer error del camp 'title'
$errors->get('title'); // Tots els errors del camp 'title' (array)
$errors->has('title'); // true si 'title' té errors
// Comptar errors
$errors->count(); // Nombre total d'errorsMostrar errors a Blade#
Tots els errors junts#
Per mostrar un resum de tots els errors al principi del formulari:
@if($errors->any())
<div class="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
<div class="flex items-center gap-2 text-red-800 font-semibold">
Hi ha {{ $errors->count() }} errors al formulari
</div>
<ul class="mt-2 text-sm text-red-700 list-disc list-inside">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endifErrors per camp#
El patró més comú és mostrar l'error al costat de cada camp del formulari:
<form method="POST" action="{{ route('articles.store') }}">
@csrf
<div class="mb-4">
<label for="title" class="block font-medium mb-1">Títol</label>
<input
type="text"
name="title"
id="title"
value="{{ old('title') }}"
class="w-full border rounded-lg px-3 py-2
@error('title') border-red-500 bg-red-50 @else border-gray-300 @enderror"
>
@error('title')
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
<div class="mb-4">
<label for="category_id" class="block font-medium mb-1">Categoria</label>
<select
name="category_id"
id="category_id"
class="w-full border rounded-lg px-3 py-2
@error('category_id') border-red-500 @enderror"
>
<option value="">Selecciona una categoria</option>
@foreach($categories as $category)
<option
value="{{ $category->id }}"
@selected(old('category_id') == $category->id)
>
{{ $category->name }}
</option>
@endforeach
</select>
@error('category_id')
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
<div class="mb-4">
<label for="body" class="block font-medium mb-1">Contingut</label>
<textarea
name="body"
id="body"
rows="6"
class="w-full border rounded-lg px-3 py-2
@error('body') border-red-500 @enderror"
>{{ old('body') }}</textarea>
@error('body')
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
<div class="mb-4">
<label class="flex items-center gap-2">
<input
type="checkbox"
name="published"
value="1"
@checked(old('published'))
>
<span>Publicar immediatament</span>
</label>
</div>
<button type="submit" class="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700">
Crear article
</button>
</form>Conservar valors amb old()#
La funció old() recupera el valor que l'usuari havia introduït abans que la validació fallés. Això és important per no obligar l'usuari a tornar a omplir tot el formulari:
{{-- Inputs de text --}}
<input type="text" name="title" value="{{ old('title') }}">
{{-- Textarea --}}
<textarea name="body">{{ old('body') }}</textarea>
{{-- Select --}}
<option value="tech" @selected(old('category') === 'tech')>Tecnologia</option>
{{-- Checkbox --}}
<input type="checkbox" name="newsletter" @checked(old('newsletter'))>
{{-- Radio --}}
<input type="radio" name="type" value="article" @checked(old('type') === 'article')>
{{-- Amb valor per defecte (per a edició) --}}
<input type="text" name="title" value="{{ old('title', $article->title) }}">El segon paràmetre de old() és el valor per defecte, que s'utilitza quan no hi ha cap valor anterior a la sessió. Això és ideal per a formularis d'edició: mostra el valor antic si la validació ha fallat, o el valor actual del model si és la primera càrrega.
Component de camp reutilitzable#
Per evitar repetir el patró d'error a cada camp, pots crear un component Blade reutilitzable:
{{-- resources/views/components/form/input.blade.php --}}
@props(['name', 'label', 'type' => 'text', 'value' => ''])
<div class="mb-4">
<label for="{{ $name }}" class="block font-medium mb-1">{{ $label }}</label>
<input
type="{{ $type }}"
name="{{ $name }}"
id="{{ $name }}"
value="{{ old($name, $value) }}"
{{ $attributes->merge([
'class' => 'w-full border rounded-lg px-3 py-2 ' .
($errors->has($name) ? 'border-red-500 bg-red-50' : 'border-gray-300')
]) }}
>
@error($name)
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
@enderror
</div>Ara pots utilitzar-lo de manera concisa:
<x-form.input name="title" label="Títol" />
<x-form.input name="email" label="Correu electrònic" type="email" />
<x-form.input name="title" label="Títol" :value="$article->title" />