Validasi yang solid menjadi sia-sia bila pengguna tidak mengerti apa yang salah. Setelah redirect 302 dari chapter Konsep Validation
, pengguna kembali ke halaman form membawa koper pesan error di session — dan tugas view untuk mengemasnya dengan jelas. Laravel membuatnya hampir tanpa usaha: variabel $errors selalu tersedia di semua view, directive @error menandai field bermasalah, dan old input mengisi kembali isian form. Chapter ini merangkai ketiganya menjadi pola halaman form yang ramah pengguna.
$errors: Error Bag di Dalam View #
Middleware ShareErrorsFromSession otomatis membagikan variabel $errors ke setiap view — instance ViewErrorBag yang kosong jika tidak ada error. Method-method utamanya:
| Ekspresi | Hasil |
|---|---|
$errors->any() | true bila ada minimal satu error |
$errors->all() | Daftar semua pesan error (array) |
$errors->has('email') | true bila field itu bermasalah |
$errors->first('email') | Pesan error pertama untuk field itu |
$errors->get('password') | Semua pesan error untuk field itu |
Sebagai panggung uji kita pakai form registrasi dengan validasi yang ketat. Feature test berikut mengirim data yang gagal di banyak field sekaligus:
public function test_form_render_setelah_validasi_gagal(): void
{
$response = $this->from('/register')->post('/register', [
'nama' => 'Bu',
'email' => 'bukan-email',
'password' => '123',
'password_confirmation' => '456',
'jenis_kelamin' => '',
'kota' => '',
'hobi' => [],
'bio' => 'Halo, saya Budi.',
]);
$response->assertStatus(302)->assertSessionHasErrors(
['nama', 'email', 'password', 'jenis_kelamin', 'kota', 'hobi']
);
}Kita inspeksi isi error bag yang tersimpan di session setelah redirect:
Output yang Dihasilkan:
errors->any(): true
errors->all():
- The nama field must be at least 3 characters.
- The email field must be a valid email address.
- The password field must be at least 8 characters.
- The password field confirmation does not match.
- The jenis kelamin field is required.
- The kota field is required.
- The hobi field is required.
errors->has('email'): true
errors->first('nama'): The nama field must be at least 3 characters.
errors->get('password'):
- The password field must be at least 8 characters.
- The password field confirmation does not match.Perhatikan bahwa field password mengumpulkan dua pesan — panjang minimum dan konfirmasi yang tidak cocok — sekaligus dalam satu list.
Kotak Ringkasan di Atas Form #
Pola pertama yang layak selalu ada: ringkasan semua error di bagian atas halaman. Pengguna langsung melihat ada berapa masalah tanpa harus menebak-nebak:
@if ($errors->any())
<div class="bg-red-50 border border-red-300 text-red-700 p-4 rounded">
<strong>Mohon periksa kembali isian Anda:</strong>
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endifDirective @error dengan $message #
Directive @error('field') membuka blok yang hanya dirender bila field tersebut bermasalah. Di dalamnya, variabel $message sudah berisi pesan error pertama field itu:
<label for="email">Email</label>
<input id="email" type="email" name="email" value="{{ old('email') }}"
class="@error('email') border-red-500 @enderror">
@error('email')
<p class="text-red-600 text-sm">{{ $message }}</p>
@enderrorPola label → input → pesan error berurutan seperti ini penting untuk aksesibilitas: pembaca layar membaca halaman dari atas ke bawah, sehingga pesan yang menempel persis di bawah fieldnya mudah diasosiasikan. Blok @error('email') border-red-500 @enderror bahkan bisa diselipkan di dalam atribut class — saat field bermasalah, class CSS penanda merah ikut tercetak, dan saat field sehat atribut class menjadi string kosong. Kita buktikan hasil render nyatanya dengan feature test: POST yang gagal diikuti GET halaman form. Untuk skenario yang bersih, semua field valid kecuali konfirmasi password:
Output yang Dihasilkan:
<div class="bg-red-50 border border-red-300 text-red-700 p-4 rounded">
<strong>Mohon periksa kembali isian Anda:</strong>
<ul>
<li>The password field confirmation does not match.</li>
</ul>
</div>
<label for="password">Password</label>
<input id="password" type="password" name="password"
class=" border-red-500 ">
<p class="text-red-600 text-sm">The password field confirmation does not match.</p>
<label for="password_confirmation">Tiga penanda tampil sekaligus: kotak ringkasan berisi satu pesan, input password membawa class border-red-500, dan paragraf pesan tepat di bawah field-nya. Isian lain seperti value="Budi" pada input nama tetap terisi — itu kerja old() yang akan kita bedah di chapter berikutnya.
Error Bag Bernama #
Halaman yang punya dua form berbeda — misalnya login dan newsletter — bisa saling menimpa error. Solusinya adalah named error bag. Saat redirect manual, kita beri nama bag melalui argumen kedua withErrors():
return redirect()->route('login-bag.show')->withErrors(
['email' => 'Kredensial tidak cocok dengan catatan kami.'],
'login'
);Di sisi view, directive @error menerima nama bag sebagai argumen kedua:
@error('email', 'login')
<p class="text-red-600 text-sm">{{ $message }}</p>
@enderrorFeature test membuktikan pesan dari bag login hanya tampil pada directive yang menyebut nama bag tersebut:
Output yang Dihasilkan:
status POST: 302
redirect ke : http://localhost:8000/login-bag
GET /login-bag:
<form method="POST" action="http://localhost:8000/login-bag">
<input type="hidden" name="_token" value="kqaLKlYX1pkO5UHl8Cx4oON…" autocomplete="off"> <label for="email">Email</label>
<input id="email" type="email" name="email" value="">
<p class="text-red-600 text-sm">Kredensial tidak cocok dengan catatan kami.</p>
<button type="submit">Masuk</button>
</form>Pesan kustom kita terrender sempurna di form login tanpa mengganggu bag default milik form lain di halaman yang sama. Di sisi test, assertion yang setara untuk kasus ini adalah assertSessionHasErrorsIn('login', ['email']) — versi bernama dari assertSessionHasErrors yang memeriksa bag tertentu di session.
Form yang mengembalikan error sebaiknya tidak membuat pengguna mengetik ulang segalanya. Laravel menyimpan input lama di session dan menyajikannya lewat helper old() — topik lengkapnya di chapter Old Input dan Form State
. Rujukan resmi: dokumentasi Blade
dan dokumentasi Errors
.