Di Validasi Server-Side
kita menulis validasi dengan tangan: memeriksa tiap field, menimbun pesan ke array $errors, menampilkan ulang nilai lama, dan mengarahkan pengguna kembali ke form. Seluruh alur itu benar — dan Laravel memformalkan alur yang sama menjadi sistem validation yang ringkas. Pada chapter ini kita pelajari $request->validate(), sintaks aturan, pesan kustom, Form Request untuk validasi reusable, Rule Object untuk aturan buatan, aturan unique yang berhubungan langsung dengan constraint database, serta penampilan error di Blade.
Validasi Langsung di Controller #
Pintu masuk paling sering dipakai adalah method validate() pada object $request. Kita serahkan array aturan, dan Laravel menjalankan seluruh alur tolak-atau-lolos secara otomatis.
<?php
// app/Http/Controllers/PostController.php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'body' => 'required|string|min:20',
'email' => 'required|email',
]);
$post = Post::create($validated);
return redirect()->route('posts.show', $post->id);
}
}Tiga hal terjadi di balik method store() di atas:
- Validasi berjalan sebelum baris lainnya: bila ada satu aturan gagal, Laravel langsung menghentikan method ini. Untuk permintaan web, pengguna diarahkan kembali ke halaman sebelumnya membawa flash message
$errorsdan input lama; untuk permintaan API yang meminta JSON, Laravel menjawab respons422 Unprocessable Contentberisi rincian error. $validatedhanya berisi kunci tervalidasi: array ini aman dioper kePost::create()karena isinya persis field yang kita deklarasikan — berlapis aman dengan$fillabledari chapter Eloquent.- Tidak ada redirect manual: kita tidak menulis
$_SESSION['errors']atauheader('Location: ...'); seluruh mekanisme yang dulu kita rakit sendiri di Menampilkan Validation Errors kini berjalan otomatis.
Dua Sintaks Aturan #
Aturan ditulis dengan string berpemisah pipa atau array — keduanya menghasilkan perilaku identik. Pilihan bentuknya soal keterbacaan: pipa ringkas untuk aturan sederhana, array wajib saat kita menyisipkan object aturan.
<?php
// Bentuk string: ringkas untuk aturan sederhana
$request->validate([
'email' => 'required|email|max:255',
]);
// Bentuk array: cocok saat aturan bertambah atau memakai object
$request->validate([
'email' => ['required', 'email', 'max:255'],
'umur' => ['required', 'integer', 'min:13'],
'website' => ['nullable', 'url'],
]);Aturan nullable pada field website layak digarisbawahi: field boleh kosong, tetapi bila terisi nilainya harus URL valid — tanpa nullable, form yang mengosongkan field opsional akan tertolak. Kumpulan aturan bawaan Laravel mencakup hampir semua kebutuhan umum:
| Kategori | Contoh aturan | Peran |
|---|---|---|
| Kehadiran | required, nullable, filled | apakah field wajib ada |
| Tipe dan format | string, integer, numeric, email, url, date, boolean | bentuk nilai yang diterima |
| Ukuran | min:20, max:255, between:1,100, size:5 | panjang atau rentang nilai |
| Konfirmasi | confirmed | field harus cocok dengan field_confirmation |
| Database | unique:users,email, exists:posts,id | nilai diperiksa ke tabel |
| Berkas | file, image, mimes:jpg,png, max:2048 | upload yang diizinkan |
Pesan Error Kustom #
Pesan bawaan Laravel berbahasa Inggris, tetapi array kedua validate() menggantinya per aturan per field. Placeholder :attribute otomatis diganti nama field jika kita ingin pesan yang menyebut kolomnya.
<?php
$validated = $request->validate(
[
'title' => 'required|string|max:255',
'email' => 'required|email',
],
[
'title.required' => 'Judul wajib diisi.',
'title.max' => 'Judul tidak boleh lebih dari 255 karakter.',
'email.required' => 'Alamat email wajib diisi.',
'email.email' => 'Format email tidak valid.',
]
);Kunci pesan berpola namafield.aturan, sehingga satu field bisa punya pesan berbeda untuk kegagalan berbeda. Untuk aplikasi berbahasa Indonesia penuh, Laravel juga menyediakan berkas terjemahan di lang/ sehingga seluruh pesan bawaan diganti sekali untuk semua form — pendekatan yang lebih terpelihara daripada menulis pesan di tiap controller.
Form Request untuk Validasi Reusable #
Ketika aturan yang sama dipakai beberapa route, atau daftar aturan tumbuh panjang, memindahkannya ke class tersendiri menjadikan controller kembali ramping. Class ini bernama Form Request: wadah aturan sekaligus otorisasi yang di-inject langsung ke signature controller.
php artisan make:request StorePostRequest<?php
// app/Http/Requests/StorePostRequest.php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'title' => 'required|string|max:255',
'body' => 'required|string|min:20',
];
}
}<?php
// app/Http/Controllers/PostController.php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Http\Requests\StorePostRequest;
use App\Models\Post;
class PostController extends Controller
{
public function store(StorePostRequest $request)
{
// baris ini hanya tereksekusi bila validasi lolos
$post = Post::create($request->validated());
return redirect()->route('posts.show', $post->id);
}
}Alurnya bekerja lewat dependency injection yang sama dengan Dependency Container
: container melihat tipe parameter StorePostRequest, membuat instancenya dari permintaan berjalan, menjalankan validasi, lalu baru memanggil store(). Bila validasi gagal, method controller tidak pernah tereksekusi. Method authorize() mengembalikan bool untuk menolak pengguna yang berhak memakai permintaan tersebut — penggabungan awal antara validasi dan authorization yang kita bahas di chapter berikutnya.
Rule Object untuk Aturan Buatan #
Kadang kebutuhan bisnis tidak tercakup aturan bawaan: larangan kata tertentu, format nomor KTP, atau kombinasi kolom yang saling bergantung. Laravel menyediakan Rule Object — class kecil yang mengimplementasikan ValidationRule — agar aturan buatan tetap teruji dan reusable.
php artisan make:rule TanpaKataSpam<?php
// app/Rules/TanpaKataSpam.php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class TanpaKataSpam implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (is_string($value) && str_contains(strtolower($value), 'beli obat')) {
$fail('Kolom :attribute tidak boleh mengandung iklan spam.');
}
}
}<?php
use App\Rules\TanpaKataSpam;
$request->validate([
'body' => ['required', 'string', new TanpaKataSpam()],
]);Method validate() menerima nama field, nilai, dan closure $fail; memanggil $fail('pesan') menandai validasi gagal dengan pesan tersebut. Karena berbentuk class, rule object bisa menerima argumen konstruktor (misalnya daftar kata yang diblokir dari konfigurasi) dan dites terpisah dengan PHPUnit — keunggulan atas closure lepas yang sulit diuji ulang.
Aturan unique dan Batasan Database #
Aturan unique memeriksa ke tabel apakah nilai sudah dipakai — validasi yang menyangkut database, bukan sekadar bentuk string. Bentuk objeknya memberi ruang penyesuaian yang tidak muat dalam string pipa.
<?php
use Illuminate\Validation\Rule;
$request->validate([
'email' => [
'required',
'email',
Rule::unique('users', 'email'),
],
]);Saat mengubah data, abaikan baris milik record yang sedang diedit dengan Rule::unique('users', 'email')->ignore($user->id), sebab email yang tidak berubah akan selalu dianggap duplikat dirinya sendiri.
Menampilkan Error di Blade #
Sisi pengguna dari validation adalah form yang menolak dengan sopan: nilai terakhir dipertahankan, dan pesan error tampil di samping field yang salah. Laravel membawa old() dan $errors ke Blade sehingga alur manual kita dulu tinggal dipanggil.
<form method="POST" action="{{ route('posts.store') }}">
@csrf
<label for="title">Judul</label>
<input id="title" name="title" value="{{ old('title') }}">
@error('title')
<p style="color: red;">{{ $message }}</p>
@enderror
<button type="submit">Simpan</button>
</form>Empat komponen template di atas masing-masing melanjutkan konsep yang sudah kita kenal. Direktif @csrf menyisipkan input tersembunyi berisi token CSRF — persis mekanisme CSRF Tokens
yang kita tulis manual. old('title') mengembalikan nilai terakhir dari flash data saat validasi gagal, sehingga pengguna tidak mengetik ulang. Direktif @error('title') hanya menampilkan isinya bila field tersebut gagal, dan $message di dalamnya sudah di-escape oleh {{ }}. Semua output melalui {{ }} di-escape otomatis, sehingga nilai pengguna yang membawa tag HTML tidak merusak halaman.
Validasi vs Sanitasi #
Bab ini sengaja menutup dengan pembedaan yang kadang dikaburkan framework. Validasi memutuskan menerima atau menolak data; sanitasi membersihkan data agar aman dipakai. validate() jelas milik kelompok pertama — data buruk ditolak, bukan diperbaiki diam-diam. Untuk sanitasi, Laravel menempatkannya di titik keluaran: {{ }} pada Blade meng-escape HTML sehingga XSS tertahan di waktu render. Rekapitulasi lengkapnya ada di Sanitasi vs Validasi
.
Ringkasan #
Kini kita memiliki spektrum lengkap validasi Laravel: validate() untuk kasus cepat, Form Request untuk aturan reusable, Rule Object untuk logika bisnis khusus, pesan kustom untuk bahasa pengguna, dan error bag old() serta @error untuk pengalaman form yang manusiawi. Lapisan ini paling bermanfaat ketika berdiri di depan data yang berharga: akun pengguna. Mengelola siapa yang boleh masuk dan apa yang boleh dilakukannya adalah topik chapter penutup Laravel: authentication dan authorization.