BAB 7: Controllers
Mengorganisir logika request-response menggunakan controller class di Laravel.
Di bab sebelumnya, middleware bertugas sebagai pos pemeriksaan — memutuskan apakah request layak diteruskan atau harus dihentikan. Setelah request melewati semua lapisan itu, ia akhirnya sampai ke controller. Selama ini kita sudah berkali-kali menyebut controller di contoh rute, tapi hanya sebagai tujuan akhir yang belum pernah kita buka isinya.
Controller adalah tempat logika utama aplikasi tinggal. Di sinilah request diterima, data diambil dari database, dan response disiapkan untuk dikembalikan ke browser. Tanpa controller, semua logika itu akan bertumpuk di file routes/web.php dan membuat routing jadi kacau.
Membuat Controller
Laravel menyediakan perintah Artisan untuk membuat controller baru:
php artisan make:controller CatatanController
Perintah ini membuat file app/Http/Controllers/CatatanController.php. Struktur defaultnya sederhana:
<?php
// app/Http/Controllers/CatatanController.php
namespace App\Http\Controllers;
class CatatanController extends Controller
{
//
}
Semua controller di Laravel ditempatkan di namespace App\Http\Controllers dan biasanya mengextend class Controller bawaan framework — meski ini bukan keharusan teknis, hanya konvensi.
Untuk menambahkan method pertama, tulis function di dalam class tersebut:
<?php
// app/Http/Controllers/CatatanController.php
namespace App\Http\Controllers;
use App\Models\Catatan;
use Illuminate\View\View;
class CatatanController extends Controller
{
public function index(): View
{
$catatanList = Catatan::latest()->get();
return view('catatan.index', compact('catatanList'));
}
}
Lalu daftarkan method ini ke rute:
// routes/web.php
use App\Http\Controllers\CatatanController;
Route::get('/catatan', [CatatanController::class, 'index']);
Ketika URL /catatan dikunjungi, Laravel memanggil method index() di CatatanController. Method mengambil semua catatan dari database dan mengirimnya ke view.
Resource Controller
Ketika membangun fitur CRUD, setiap resource biasanya butuh tujuh operasi yang sama: menampilkan daftar, form tambah, simpan, tampil detail, form edit, update, dan hapus. Laravel memiliki konvensi untuk ini — disebut resource controller.
Buat resource controller dengan flag --resource:
php artisan make:controller CatatanController --resource
Controller yang dihasilkan sudah memiliki tujuh method yang terisi skeleton:
<?php
// app/Http/Controllers/CatatanController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class CatatanController extends Controller
{
// Menampilkan daftar semua catatan
public function index() {}
// Menampilkan form untuk membuat catatan baru
public function create() {}
// Menyimpan catatan baru ke database
public function store(Request $request) {}
// Menampilkan satu catatan berdasarkan ID
public function show(string $id) {}
// Menampilkan form edit catatan
public function edit(string $id) {}
// Menyimpan perubahan catatan ke database
public function update(Request $request, string $id) {}
// Menghapus catatan dari database
public function destroy(string $id) {}
}
Daftarkan semua tujuh rute sekaligus dengan satu baris:
// routes/web.php
use App\Http\Controllers\CatatanController;
Route::resource('catatan', CatatanController::class);
Satu baris ini menghasilkan tujuh rute yang terpetakan ke tujuh method di atas:
| Method HTTP | URL | Method Controller | Nama Rute |
|---|---|---|---|
| GET | /catatan | index | catatan.index |
| GET | /catatan/create | create | catatan.create |
| POST | /catatan | store | catatan.store |
| GET | /catatan/{catatan} | show | catatan.show |
| GET | /catatan/{catatan}/edit | edit | catatan.edit |
| PUT/PATCH | /catatan/{catatan} | update | catatan.update |
| DELETE | /catatan/{catatan} | destroy | catatan.destroy |
Nama rute ini berguna di view untuk membuat link dan form action tanpa hardcode URL:
// Di Blade template
<a href="{{ route('catatan.index') }}">Semua Catatan</a>
<a href="{{ route('catatan.edit', $catatan) }}">Edit</a>
Membatasi Method yang Dihasilkan
Tidak selalu semua tujuh method dibutuhkan. Gunakan only() untuk memilih yang diperlukan, atau except() untuk mengecualikan yang tidak perlu:
// routes/web.php
// Hanya tampil dan detail — tidak ada form tambah/edit/hapus
Route::resource('catatan', CatatanController::class)->only([
'index', 'show'
]);
// Semua kecuali create dan edit (biasanya untuk API)
Route::resource('catatan', CatatanController::class)->except([
'create', 'edit'
]);
API Resource Controller
Untuk API, method create dan edit tidak dibutuhkan karena API tidak mengembalikan form HTML. Buat controller khusus API dengan flag --api:
php artisan make:controller Api/CatatanController --api
Atau gunakan apiResource() saat mendaftarkan rute:
// routes/api.php
Route::apiResource('catatan', CatatanController::class);
Single Action Controller
Terkadang sebuah controller hanya butuh satu action — misalnya controller untuk proses pembayaran, ekspor data, atau halaman landing khusus. Untuk kasus ini, gunakan invokable controller dengan method __invoke():
php artisan make:controller EksporCatatanController --invokable
<?php
// app/Http/Controllers/EksporCatatanController.php
namespace App\Http\Controllers;
use App\Models\Catatan;
use Illuminate\Http\Response;
class EksporCatatanController extends Controller
{
public function __invoke(): Response
{
$catatan = Catatan::all();
$isi = $catatan->map(fn($c) => $c->judul . ': ' . $c->isi)->join("\n");
return response($isi)
->header('Content-Type', 'text/plain')
->header('Content-Disposition', 'attachment; filename="catatan.txt"');
}
}
Saat mendaftarkan rute, tidak perlu menyebut nama method — langsung gunakan nama class saja:
// routes/web.php
use App\Http\Controllers\EksporCatatanController;
Route::get('/catatan/ekspor', EksporCatatanController::class);
Dependency Injection di Controller
Laravel memiliki service container yang bisa otomatis menyuntikkan dependency ke controller. Ini membuat controller mudah diuji karena dependensinya bisa diganti saat testing.
Constructor Injection
Suntikkan dependency di constructor agar tersedia di seluruh method controller:
<?php
// app/Http/Controllers/CatatanController.php
namespace App\Http\Controllers;
use App\Repositories\CatatanRepository;
class CatatanController extends Controller
{
public function __construct(
private CatatanRepository $catatanRepo
) {}
public function index()
{
$catatanList = $this->catatanRepo->semuaCatatan();
return view('catatan.index', compact('catatanList'));
}
public function show(string $id)
{
$catatan = $this->catatanRepo->cariById($id);
return view('catatan.show', compact('catatan'));
}
}
Method Injection
Dependency juga bisa disuntikkan langsung ke parameter method. Ini berguna untuk dependency yang hanya dibutuhkan satu method tertentu:
<?php
// app/Http/Controllers/CatatanController.php
namespace App\Http\Controllers;
use App\Models\Catatan;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class CatatanController extends Controller
{
public function store(Request $request): RedirectResponse
{
$catatan = Catatan::create([
'judul' => $request->judul,
'isi' => $request->isi,
]);
return redirect()->route('catatan.show', $catatan)
->with('sukses', 'Catatan berhasil disimpan.');
}
}
Ketika rute memiliki parameter, tambahkan di parameter method setelah dependency:
// routes/web.php
Route::put('/catatan/{id}', [CatatanController::class, 'update']);
// Controller
public function update(Request $request, string $id): RedirectResponse
{
$catatan = Catatan::findOrFail($id);
$catatan->update([
'judul' => $request->judul,
'isi' => $request->isi,
]);
return redirect()->route('catatan.show', $catatan)
->with('sukses', 'Catatan berhasil diperbarui.');
}
Laravel tahu untuk meneruskan $request sebagai injeksi dan $id dari parameter rute — keduanya bisa hadir bersamaan di satu method.
Middleware di Controller
Di bab sebelumnya kita menerapkan middleware langsung di rute. Alternatifnya, middleware bisa didefinisikan di dalam controller menggunakan interface HasMiddleware:
<?php
// app/Http/Controllers/CatatanController.php
namespace App\Http\Controllers;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
class CatatanController extends Controller implements HasMiddleware
{
public static function middleware(): array
{
return [
'auth',
new Middleware('verified', only: ['store', 'update', 'destroy']),
];
}
// method-method controller...
}
Dengan cara ini, semua konfigurasi middleware untuk controller terkumpul di satu tempat. Middleware auth berlaku untuk semua method, sementara verified hanya berlaku untuk operasi yang mengubah data.
Pilih di mana mendefinisikan middleware berdasarkan scope kebutuhannya. Middleware di rute cocok ketika satu middleware digunakan di banyak controller yang berbeda. Middleware di controller cocok ketika konfigurasi middleware spesifik untuk controller itu saja.
Route Model Binding
Saat controller menerima ID dari URL, pola umumnya adalah memanggil findOrFail() secara manual. Laravel memiliki cara yang lebih ringkas: route model binding.
Alih-alih menerima string $id dan mencari model secara manual, deklarasikan model langsung sebagai tipe parameter:
<?php
// app/Http/Controllers/CatatanController.php
namespace App\Http\Controllers;
use App\Models\Catatan;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class CatatanController extends Controller
{
// Tanpa route model binding — manual
public function showManual(string $id): View
{
$catatan = Catatan::findOrFail($id); // bisa 404 otomatis
return view('catatan.show', compact('catatan'));
}
// Dengan route model binding — otomatis
public function show(Catatan $catatan): View
{
return view('catatan.show', compact('catatan'));
}
public function edit(Catatan $catatan): View
{
return view('catatan.edit', compact('catatan'));
}
public function update(Request $request, Catatan $catatan): RedirectResponse
{
$catatan->update($request->only(['judul', 'isi']));
return redirect()->route('catatan.show', $catatan);
}
public function destroy(Catatan $catatan): RedirectResponse
{
$catatan->delete();
return redirect()->route('catatan.index')
->with('sukses', 'Catatan berhasil dihapus.');
}
}
Laravel mencocokkan nama parameter di URL dengan nama parameter di method controller. Ketika URL /catatan/{catatan} dipanggil dengan ID 5, Laravel otomatis mencari Catatan::find(5). Jika tidak ditemukan, Laravel langsung mengembalikan response 404 — tanpa satu baris kode tambahan.
Nama parameter di rute harus cocok dengan nama parameter di method controller. Rute {catatan} akan cocok dengan Catatan $catatan, bukan Catatan $id. Pastikan keduanya konsisten.
Nested Resource Controller
Ketika resource memiliki hubungan parent-child — misalnya komentar yang dimiliki oleh catatan — gunakan nested resource:
php artisan make:controller KomentarController --resource
// routes/web.php
Route::resource('catatan.komentar', KomentarController::class);
Ini menghasilkan URL seperti /catatan/{catatan}/komentar/{komentar}. Di controller, kedua model bisa diterima sekaligus:
<?php
// app/Http/Controllers/KomentarController.php
namespace App\Http\Controllers;
use App\Models\Catatan;
use App\Models\Komentar;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class KomentarController extends Controller
{
public function index(Catatan $catatan): View
{
return view('komentar.index', [
'catatan' => $catatan,
'komentarList' => $catatan->komentar()->latest()->get(),
]);
}
public function store(Request $request, Catatan $catatan): RedirectResponse
{
$catatan->komentar()->create([
'isi' => $request->isi,
'user_id' => $request->user()->id,
]);
return redirect()->route('catatan.komentar.index', $catatan)
->with('sukses', 'Komentar ditambahkan.');
}
public function destroy(Catatan $catatan, Komentar $komentar): RedirectResponse
{
$komentar->delete();
return redirect()->route('catatan.komentar.index', $catatan);
}
}
Latihan
Coba kerjakan latihan berikut menggunakan konsep yang sudah dipelajari di bab ini dan bab-bab sebelumnya:
-
Buat resource controller — Buat
TagControllerdengan--resource. Implementasikan methodindex()yang mengambil semua tag dari database dan mengirimnya ke viewtag/index.blade.php. Daftarkan rutenya dan pastikan halaman bisa diakses. -
Route model binding — Ubah method
show()danedit()diCatatanControlleragar menggunakan route model binding. Verifikasi bahwa mengakses ID yang tidak ada secara otomatis menghasilkan halaman 404. -
Invokable controller — Buat
RingkasanControlleryang mengimplementasikan__invoke()untuk menghitung dan menampilkan statistik: total catatan, catatan hari ini, dan catatan bulan ini. Daftarkan di rute/ringkasan.
Penutup Bab
Controller adalah penghubung antara rute dan logika aplikasi. Dengan resource controller, tujuh operasi CRUD bisa terdaftar dan terorganisir hanya dengan beberapa baris kode. Route model binding menghilangkan boilerplate pencarian dan penanganan 404 secara manual. Dependency injection membuat controller tetap bersih dan mudah diuji.
Semua yang masuk ke controller datang dalam bentuk Request — sebuah objek yang menyimpan seluruh informasi dari request HTTP: input form, query string, file upload, header, hingga cookie. Bab berikutnya akan membedah objek Request ini secara menyeluruh, sehingga kamu tahu persis cara mengakses data apapun yang dikirim user ke aplikasi.