Episode #74 mengajarkan alur request, episode #75 mengajarkan menyimpan data. Tapi ada yang hilang di tengah: bagaimana data masuk dari browser ke aplikasimu? Jawabannya form HTML. Dan begitu kamu membuka pintu input untuk pengunjung, kamu membuka pintu untuk data ngawur, data jahat, dan file berbahaya. Episode ini tentang cara menerima input dengan aman: form, validasi, dan upload file.
Posisinya penting: hampir semua aplikasi interaktif butuh form. Komentar blog, formulir pendaftaran, upload foto profil, semuanya lewat sini. Validasi bukan fitur tambahan yang "nanti saja", melainkan bagian inti dari setiap form yang kamu bikin mulai hari ini.
Persepsi yang perlu diluruskan dulu
Salah kaprah 1: "Validasi cukup di browser pakai required atau JavaScript." Atribut required di HTML dan cek JavaScript memang bikin UX lebih enak, tapi keduanya jalan di komputer pengunjung. Siapa pun bisa mematikan JavaScript, mengedit HTML lewat dev tools, atau mengirim request langsung pakai curl tanpa lewat form sama sekali. Validasi yang benar-benar melindungi aplikasimu hanya yang jalan di server. Validasi browser itu pemanis, validasi server itu pengaman.
Salah kaprah 2: "Upload file tinggal simpan, kan Laravel yang ngurus." Laravel menyediakan tools-nya, tapi keputusan keamanannya tetap di tanganmu. File yang diupload bisa jadi script PHP berkedok gambar, ukurannya bisa 2 GB dan memenuhi disk server, atau namanya menimpa file penting. Upload tanpa validasi dan penyimpanan yang benar adalah salah satu celah paling klasik di aplikasi web.
Form Blade dan @csrf: surat dengan stempel
Form HTML biasa di Blade terlihat seperti ini:
<form action="{{ route('artikel.store') }}" method="POST">
@csrf
<input type="text" name="judul">
<textarea name="isi"></textarea>
<button type="submit">Simpan</button>
</form>
Yang paling penting di sini adalah @csrf. Direktif ini menghasilkan input hidden berisi token rahasia yang hanya diketahui aplikasimu dan browser pengunjung yang sah.
Analoginya seperti surat dengan stempel khusus. Bayangkan seseorang bisa memalsukan surat perintah transfer atas namamu. Stempel rahasia memastikan surat itu benar-benar berasal dari mejamu, bukan dari orang luar yang meniru kop suratmu. Tanpa token ini, penyerang bisa membuat halaman jebakan yang diam-diam mengirim form ke aplikasimu lewat browser korban yang sedang login. Itu serangan CSRF, dan @csrf menutupnya.
Berhenti dan tebak dulu! Kamu lupa menulis @csrf di form, lalu menekan submit. Apa yang terjadi dan kenapa?
<form action="{{ route('artikel.store') }}" method="POST">
<!-- @csrf ketinggalan -->
<input type="text" name="judul">
</form>
Validasi dengan $request->validate()
Di controller, validasi paling sederhana ditulis begini:
public function store(Request $request)
{
$validated = $request->validate([
'judul' => 'required|string|max:255',
'isi' => 'required|min:10',
]);
Artikel::create($validated);
return redirect()->route('artikel.index');
}
Alur berpikirnya: $request->validate() memeriksa input sesuai aturan. Kalau lolos, ia mengembalikan hanya data yang valid (lebih aman daripada memakai $request->all()). Kalau gagal, Laravel otomatis mengembalikan pengunjung ke form sebelumnya sambil membawa pesan error dan input lama. Kamu tidak perlu menulis if gagal manual.
Aturan validasi dibaca seperti kalimat: judul wajib diisi, harus string, maksimal 255 karakter. isi wajib diisi, minimal 10 karakter. Laravel punya puluhan aturan siap pakai: email, numeric, date, unique, confirmed, dan banyak lagi.
Menampilkan error di Blade juga sudah disiapkan:
<input type="text" name="judul" value="{{ old('judul') }}">
@error('judul')
<p style="color:red;">{{ $message }}</p>
@enderror
old('judul') mengisi kembali input terakhir supaya pengunjung tidak mengetik ulang semuanya gara-gara satu field salah. Detail kecil seperti ini yang membedakan form yang nyaman dari yang menyebalkan.
Form Request: validasi pindah rumah
Saat aturan validasi mulai panjang, controller jadi penuh sesak. Solusinya: pindahkan ke kelas Form Request.
php artisan make:request StoreArtikelRequest
class StoreArtikelRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'judul' => 'required|string|max:255',
'isi' => 'required|min:10',
];
}
}
Lalu controller cukup type-hint kelas ini:
public function store(StoreArtikelRequest $request)
{
Artikel::create($request->validated());
return redirect()->route('artikel.index');
}
Kenapa ini lebih baik? Prinsipnya sama seperti controller di episode #74: setiap hal punya rumahnya sendiri. Aturan validasi adalah satu "urusan" yang utuh, jadi ia pantas punya file sendiri. Controller kembali ramping dan fokus pada alur: terima data valid, simpan, redirect. Bonusnya, Form Request yang sama bisa dipakai ulang di method update tanpa duplikasi aturan.
Kuis cepat: Method authorize() di Form Request bawaan artisan mengembalikan false. Apa akibatnya?
Upload file dengan aman
Form upload butuh satu atribut tambahan: enctype="multipart/form-data". Tanpa ini, file tidak ikut terkirim dan $request->file() akan kosong.
<form action="{{ route('foto.store') }}" method="POST" enctype="multipart/form-data">
@csrf
<input type="file" name="foto">
<button type="submit">Upload</button>
</form>
Di controller, validasi file harus memeriksa isi aslinya, bukan cuma namanya:
$validated = $request->validate([
'foto' => 'required|image|mimes:jpg,jpeg,png,webp|max:2048',
]);
$path = $request->file('foto')->store('foto-profil', 'public');
Bedah aturannya: image memastikan PHP bisa membaca file itu sebagai gambar beneran (file teks yang diganti ekstensinya jadi .jpg akan gagal di sini). mimes memeriksa tipe MIME dari isi byte file, bukan dari nama file yang bisa dipalsukan. max:2048 membatasi 2048 kilobyte alias 2 MB.
Berhenti dan tebak dulu! Aturan max:2048 pada validasi file artinya maksimal berapa? Dan kenapa satuannya penting dipahami?
'foto' => 'required|image|max:2048',
Method store('foto-profil', 'public') menyimpan file ke disk public dengan nama acak yang aman. Nama acak ini penting: kalau memakai nama asli dari pengunjung, dua file bernama sama akan saling menimpa, dan nama file jahat seperti ../../../.env bisa dieksploitasi. Laravel mengurusnya dengan menghasilkan nama unik, kamu tinggal menyimpan $path ke database.
Satu langkah yang sering kelupaan: jalankan php artisan storage:link sekali. Perintah ini membuat symlink dari public/storage ke storage/app/public supaya file yang disimpan bisa diakses browser. Tanpa ini, upload sukses tapi gambarnya 404 saat ditampilkan.
Ringkasan cepat
Lima hal untuk diingat: pertama, setiap form POST wajib punya @csrf sebagai bukti form itu berasal dari aplikasimu sendiri. Kedua, validasi yang melindungi aplikasi hanya yang jalan di server, validasi browser cuma pemanis UX. Ketiga, $request->validate() mengembalikan hanya data valid dan otomatis mengembalikan user ke form dengan pesan error. Keempat, pindahkan aturan panjang ke Form Request supaya controller tetap ramping dan aturan bisa dipakai ulang. Kelima, upload file butuh enctype di form, validasi image/mimes/max (dalam kilobyte), penyimpanan dengan nama acak via store(), dan php artisan storage:link supaya bisa diakses browser.
Latihan dan langkah berikutnya
Latihan (jangan cuma dibaca):
- Bikin form tambah buku (judul, penulis, tahun). Tambahkan validasi: tahun harus numeric dan antara 1900 sampai tahun sekarang. Tampilkan error di bawah tiap input dengan @error.
- Sengaja kirim form kosong dan perhatikan pesan error bawaan Laravel. Lalu ubah pesannya jadi Bahasa Indonesia lewat parameter ketiga atau file lang. Rasakan bedanya dari sisi pengguna.
- Tambahkan field upload sampul buku (image, maks 2 MB). Coba upload file teks yang diganti ekstensi .jpg dan lihat validasi mana yang menolaknya. Ini bukti kenapa aturan image memeriksa isi, bukan nama.
- Refactor: pindahkan aturan validasimu ke Form Request dan sederhanakan controller sampai tinggal 3 baris.
Episode #77 memakai semua yang kamu pelajari hari ini untuk fitur yang hampir wajib di setiap aplikasi: login dan register. Form-nya, validasinya, keamanannya, semuanya naik satu level karena sekarang yang dipertaruhkan adalah akun pengguna.
Diskusi & Komentar 0
Punya tanggapan, pertanyaan, atau tips seputar tutorial ini? Tulis komentar kamu di bawah!
Jadilah yang pertama membuka obrolan dan berbagi pandangan seputar tutorial resep kode ini.
Tulis Komentar
Komentar menggunakan akun Google. Harap berkomentar dengan santun dan berbobot.