Controller & response
Controller yang baik itu tipis: ia menerjemahkan HTTP jadi panggilan ke domain, lalu menerjemahkan hasilnya kembali jadi HTTP. Semua kecacatan arsitektur aplikasi Laravel bermula dari controller yang gemuk.
Intisari
- Controller resource punya tujuh method dengan nama baku. Ikuti namanya โ seluruh ekosistem mengandalkannya.
- Nilai kembalian otomatis dikonversi:
arraydan model jadi JSON, string jadi HTML,view()jadi halaman. - Single action controller (
__invoke) untuk aksi yang tidak punya saudara. - Dependensi diketik di parameter method โ container yang menyuntikkannya (Fase 3).
- Aturan sehat: controller tidak menyentuh query kompleks, tidak mengirim email, tidak menghitung harga.
Controller resource
class ProdukController extends Controller
{
public function index(): View
{
return view('produk.index', [
'produk' => Produk::query()
->with('kategori') // cegah N+1 โ Fase 2
->latest()
->paginate(20),
]);
}
public function show(Produk $produk): View // route model binding
{
return view('produk.show', compact('produk'));
}
public function store(SimpanProdukRequest $request): RedirectResponse
{
// Sudah tervalidasi DAN sudah terotorisasi sebelum baris ini berjalan.
$produk = Produk::create($request->validated());
return redirect()
->route('produk.show', $produk)
->with('sukses', 'Produk tersimpan.');
}
public function destroy(Produk $produk): RedirectResponse
{
$this->authorize('delete', $produk); // Fase 4
$produk->delete();
return back()->with('sukses', 'Produk dihapus.');
}
}
Perhatikan yang tidak ada di sana. Tidak ada if ($request->name == '') โ
itu tugas Form Request. Tidak ada if ($user->id != $produk->user_id) โ itu tugas policy.
Tidak ada perhitungan harga โ itu tugas kelas domain. Controller tipis bukan gaya penulisan, melainkan hasil
dari memindahkan setiap urusan ke tempat yang sudah disediakan Laravel.
Apa pun yang kamu kembalikan, jadi respons
| Kembalikan | Hasilnya |
|---|---|
view('produk.index', [...]) | HTML 200 |
['nama' => 'Ani'] | JSON 200, Content-Type otomatis |
| Model atau Collection Eloquent | JSON โ hati-hati, semua kolom ikut terbawa |
redirect()->route('produk.index') | 302 |
back()->withErrors($e) | 302 ke halaman sebelumnya |
response()->json($data, 201) | JSON dengan status pilihanmu |
response()->noContent() | 204 โ untuk destroy di API |
abort(404) / abort(403, 'pesan') | Hentikan dan lempar error |
Storage::download($path) | Unduhan berkas |
Mengembalikan model langsung itu kebocoran data yang menunggu terjadi.
return $user; mengirim semua kolom yang tidak ada di $hidden โ termasuk
email, remember_token, atau kolom internal yang baru saja ditambahkan rekanmu lewat
migrasi. Untuk API, pakai API Resource (Fase 5) supaya bentuk keluaran menjadi keputusan sadar.
Single action controller
// php artisan make:controller UnduhInvoice --invokable
class UnduhInvoice extends Controller
{
public function __invoke(Invoice $invoice)
{
$this->authorize('view', $invoice);
return Storage::download($invoice->path, "invoice-{$invoice->nomor}.pdf");
}
}
Route::get('/invoice/{invoice}/unduh', UnduhInvoice::class)->name('invoice.unduh');
Pola ini menyelamatkanmu dari controller bernama MiscController berisi dua belas method yang tidak
saling berhubungan. Kalau sebuah aksi tidak punya saudara yang jelas, ia layak punya kelas sendiri.
Injeksi dependensi di method
public function store(
SimpanPesananRequest $request, // form request โ divalidasi otomatis
Pesanan $pesanan, // route model binding
GerbangPembayaran $gerbang, // di-resolve dari container (Fase 3)
): RedirectResponse {
$gerbang->tagih($pesanan, $request->integer('jumlah'));
return back();
}
Laravel membaca tipe setiap parameter dan mengisinya sendiri. Inilah alasan menulis tipe di Laravel bukan sekadar dokumentasi โ tipe adalah instruksi untuk container.
Tanda controller sudah terlalu gemuk
- Satu method lebih dari sekitar 20 baris.
- Ada
DB::atau query builder panjang di dalamnya. - Ada perhitungan bisnis โ pajak, diskon, ongkir.
- Method-nya sulit dijelaskan tanpa kata "dan": "menyimpan pesanan dan mengirim email dan memotong stok".
- Logika yang sama muncul lagi di perintah Artisan atau job.
Setiap butir di atas punya obat yang sudah disediakan framework: query scope (Fase 2), event dan job (Fase 3),
policy (Fase 4), atau sekadar satu kelas action di app/Actions.
Latihan: buat ProdukController --resource, isi index dan show,
lalu tambahkan satu single action controller EksporProduk yang mengembalikan CSV lewat
response()->streamDownload(...). Cek hasilnya dengan curl -i dan pastikan
header Content-Type-nya benar.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.