Validasi
Validasi bukan soal pesan error yang cantik. Ia adalah batas keamanan: satu-satunya tempat kamu memutuskan bentuk data apa yang boleh masuk ke aplikasimu.
Intisari
- Form Request memindahkan aturan keluar dari controller dan menyatukannya dengan otorisasi.
- Gagal validasi otomatis jadi redirect + error di session untuk web, dan JSON 422 untuk permintaan yang mengharap JSON.
- Pakai
$request->validated(), bukan$request->all()โ inilah pertahanan terhadap mass assignment. - Aturan objek (
Password::defaults(),Rule::unique(),File::image()) lebih aman daripada string yang disambung. prepareForValidation()untuk merapikan input sebelum aturan dijalankan.
Form Request: bentuk yang dipakai di proyek nyata
php artisan make:request SimpanProdukRequest
class SimpanProdukRequest extends FormRequest
{
// Dijalankan LEBIH DULU. false di sini = 403, aturan tidak pernah dievaluasi.
public function authorize(): bool
{
return $this->user()->can('create', Produk::class);
}
public function rules(): array
{
return [
'nama' => ['required', 'string', 'max:120'],
'slug' => ['required', 'alpha_dash', Rule::unique('produk')->ignore($this->produk)],
'harga' => ['required', 'integer', 'min:0'],
'kategori_id' => ['required', 'exists:kategori,id'],
'status' => ['required', Rule::enum(StatusProduk::class)],
'tag' => ['array', 'max:10'],
'tag.*' => ['string', 'max:30'],
'foto' => ['nullable', File::image()->max(2 * 1024)],
];
}
// Bersihkan input sebelum aturan berjalan
protected function prepareForValidation(): void
{
$this->merge([
'slug' => Str::slug($this->input('slug') ?: $this->input('nama')),
]);
}
public function messages(): array
{
return [
'harga.min' => 'Harga tidak boleh negatif.',
];
}
}
public function store(SimpanProdukRequest $request)
{
$produk = Produk::create($request->validated()); // โ hanya field yang lolos aturan
return redirect()->route('produk.show', $produk);
}
Inilah alasan sebenarnya memakai validated(). Ia mengembalikan hanya field yang
punya aturan. Kalau penyerang menambahkan is_admin=1 ke form, field itu tidak muncul di hasilnya,
jadi tidak mungkin ikut tersimpan. $request->all() membawa segalanya dan menyerahkan seluruh
pertahanan ke properti $fillable di model โ satu lapis, bukan dua.
Aturan yang paling sering dipakai
| Aturan | Artinya | Catatan |
|---|---|---|
required | Ada dan tidak kosong | Beda dengan present (boleh kosong) |
nullable | Boleh null | Tanpa ini, null gagal di aturan lain |
sometimes | Validasi hanya kalau field-nya dikirim | Kunci untuk PATCH |
exists:tabel,kolom | Nilainya ada di database | Satu query per field |
unique:tabel,kolom | Belum dipakai | Saat update, wajib ->ignore($model) |
confirmed | Ada field <nama>_confirmation yang sama | Untuk password |
in:a,b,c | Salah satu dari daftar | Untuk enum, pakai Rule::enum() |
after:tanggal_mulai | Perbandingan antar field | Nama field, bukan nilai |
Aturan objek: lebih aman daripada string
'password' => ['required', 'confirmed', Password::defaults()],
'email' => ['required', 'email', Rule::unique('users')->ignore($this->user)],
'peran' => ['required', Rule::in(['admin', 'staf'])],
'dokumen' => ['required', File::types(['pdf'])->max(10 * 1024)],
Keunggulannya bukan estetika: string seperti 'unique:users,email,'.$id gampang salah saat ada koma
di data, dan tidak terbaca oleh analisis statis. Bentuk objek diperiksa oleh PHPStan di Fase 6.
Aturan buatan sendiri
// php artisan make:rule KodePosIndonesia
class KodePosIndonesia implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (! preg_match('/^\d{5}$/', (string) $value)) {
$fail('Kode pos harus lima digit angka.');
}
}
}
Bentuk respons kegagalan
| Jenis permintaan | Respons | Cara membacanya |
|---|---|---|
| Form biasa (web) | 302 kembali, input lama tersimpan | @error('nama') di Blade |
Accept: application/json | 422 berisi objek errors | Ditangani frontend |
Gagal authorize() | 403 | Aturan tidak dievaluasi sama sekali |
{
"message": "The given data was invalid.",
"errors": {
"harga": ["Harga tidak boleh negatif."],
"slug": ["The slug has already been taken."]
}
}
Validasi bukan pengganti aturan database. Antara unique diperiksa dan baris disimpan ada
jeda; dua permintaan bersamaan bisa lolos keduanya. Validasi menghasilkan pesan yang enak dibaca manusia,
unique index di database yang menjamin kebenarannya. Pasang keduanya.
Latihan: buat SimpanProdukRequest lengkap dengan authorize() yang mengembalikan
false, lalu kirim permintaan dan pastikan kamu menerima 403, bukan 422 โ bukti
bahwa otorisasi berjalan lebih dulu. Kembalikan ke true, kirim data tanpa harga, dan
bandingkan respons curl biasa dengan curl -H "Accept: application/json".
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.