Form Request: Validasi yang Rapi dan Reusable
Pindahkan aturan validasi ke class khusus yang testable.
Masalah Validasi Inline
$request->validate() di controller enak untuk form kecil. Tapi begitu aturannya 15 baris, dipakai di dua method (store dan update dengan sedikit perbedaan), plus butuh pesan custom dan logika otorisasi, controller-mu berubah menjadi tempat sampah validasi. Lebih parah lagi: aturan itu tidak bisa dipakai ulang dan susah di-test secara terisolasi. Form Request menyelesaikan ketiganya dengan memindahkan validasi ke class khusus.
Membuat Form Request
php artisan make:request StorePostRequest// app/Http/Requests/StorePostRequest.php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
// true = semua orang boleh lanjut ke validasi
// di sinilah tempat cek "bolehkah user ini melakukan aksi ini?"
return $this->user()?->can('create', Post::class) ?? false;
}
public function rules(): array
{
return [
'title' => 'required|string|max:255',
'body' => 'required|string|min:10',
'category_id' => 'required|exists:categories,id',
'tags' => 'nullable|array',
'tags.*' => 'exists:tags,id', // validasi tiap elemen array
];
}
public function messages(): array
{
return [
'title.required' => 'Judul wajib diisi!',
'category_id.exists' => 'Kategori yang dipilih tidak valid.',
];
}
public function attributes(): array
{
return ['category_id' => 'kategori'];
}
}Controller-mu sekarang tinggal satu baris:
// app/Http/Controllers/PostController.php
public function store(StorePostRequest $request)
{
Post::create($request->validated());
}Type-hint StorePostRequest di parameter method membuat Laravel menjalankan authorize() lalu rules() secara otomatis SEBELUM method controller dieksekusi. Kalau otorisasi gagal, user dapat 403. Kalau validasi gagal, web dapat redirect back dan API dapat 422. Controller tidak tahu menahu soal validasi, ia hanya menerima data yang sudah bersih.
Hooks yang Jarang Diketahui
Form Request punya dua hook yang sangat berguna. prepareForValidation() berjalan sebelum aturan dicek, cocok untuk merapikan input:
// app/Http/Requests/StorePostRequest.php
protected function prepareForValidation(): void
{
$this->merge([
'slug' => str($this->title)->slug()->toString(),
]);
}Dan passedValidation() berjalan setelah lolos, cocok untuk transformasi akhir. Ada juga withValidator() untuk aturan yang butuh logika kondisional kompleks:
use Illuminate\Validation\Validator;
public function withValidator(Validator $validator): void
{
$validator->after(function (Validator $validator) {
if ($this->is_premium && ! $this->user()->is_premium) {
$validator->errors()->add('is_premium', 'Upgrade dulu untuk post premium.');
}
});
}Jebakan Umum
Pertama, authorize() default bawaan generator adalah false. Kalau kamu lupa mengubahnya menjadi true atau logika yang benar, semua request dapat 403 dan kamu bingung sendiri. Selalu periksa method ini dulu saat debugging 403 misterius.
Kedua, memakai $request->all() padahal sudah punya Form Request. Itu membuang seluruh manfaatnya. Selalu pakai $request->validated(), atau lebih ketat lagi $request->safe()->only(['title', 'body']) kalau kamu hanya butuh subset field.
Ketiga, duplikasi antara StorePostRequest dan UpdatePostRequest yang 90% sama. Untuk kasus update, bedanya biasanya cuma aturan unique yang butuh ignore(). Kamu bisa membuat satu base request atau memakai Rule::unique(...)->ignore($this->route('post')) dengan route model binding, sehingga satu class melayani dua method.
Catatan teknis: Form Request adalah contoh bagus separation of concerns: controller mengurus alur, request mengurus "apakah input ini boleh masuk". Bonusnya, class ini gampang di-unit-test tanpa harus me-render halaman: instantiate request, panggil
rules(), jalankan validator manual. Validasi yang bisa di-test adalah validasi yang berani kamu ubah.
Tantangan
Refactor ke Form Request
Ambil satu controller yang validasinya inline, pindahkan ke Form Request. Tambahkan authorize() yang hanya mengizinkan user terverifikasi (auth()->user()->email_verified). Test dengan user belum verifikasi.