Kalau kamu sudah pernah nulis controller Laravel, pasti pernah lihat kode kayak gini:
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
Dan kamu mungkin sempat bingung — “Di mana saya ambil $post-nya? Kok tiba-tiba ada?”
Itu bukan sulap. Itu Route Model Binding.
Di artikel ini, kita bahas dari dasarnya dulu — kenapa fitur ini ada, apa masalah yang dia selesaikan — baru masuk ke tiga jenisnya: implicit, explicit, dan custom.
🤔 Masalah yang Diselesaikan
Tanpa Route Model Binding, kode kamu biasanya keliatan begini:
// routes/web.php
Route::get('/posts/{id}', [PostController::class, 'show']);
// app/Http/Controllers/PostController.php
public function show($id)
{
$post = Post::findOrFail($id);
return view('posts.show', compact('post'));
}
Masalahnya bukan kodenya salah. Tapi kalau kamu punya 10 resource (Post, User, Comment, Product…), kamu bakal nulis findOrFail() di setiap controller, setiap method. Berulang-ulang. Sama persis.
Ini yang disebut boilerplate — kode yang monoton, tidak menambah nilai, tapi harus tetap ada.
Route Model Binding adalah cara Laravel bilang: “Tenang, bagian itu biar aku yang urus.”
🪄 Implicit Binding — “Yang Paling Sering Dipakai”
Analoginya
Bayangin kamu pesan makanan di restoran. Kamu cukup bilang: “Nomor meja 5.”
Pelayan langsung tahu harus ngambil pesanan siapa, tanpa kamu perlu jelasin “ambil dari database, cari id = 5, return objeknya.”
Itulah implicit binding. Laravel otomatis resolve model berdasarkan nama parameter di route dan type-hint di method controller — tanpa kamu nulis apapun secara eksplisit.
Syaratnya
Supaya implicit binding bekerja, ada dua syarat:
- Nama parameter di route harus sama dengan nama parameter di controller.
- Controller harus pakai type-hint model yang sesuai.
Kodenya
📁 routes/web.php
// Nama parameter: {post}
Route::get('/posts/{post}', [PostController::class, 'show']);
📁 app/Http/Controllers/PostController.php
// Type-hint: Post $post
// Nama parameter: $post — harus sama dengan {post} di route
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
📌 Laravel akan otomatis menjalankan Post::findOrFail($id) di balik layar. Kalau datanya tidak ada, langsung lempar 404 — tidak perlu kamu tangani sendiri.
🔑 Implicit Binding dengan Field Kustom
Secara default, implicit binding pakai primary key (id). Tapi kadang URL yang lebih baik itu pakai slug, bukan angka:
/posts/cara-belajar-laravel ← lebih bagus dari /posts/42
Untuk kasus ini, ada dua cara.
Cara 1 — Inline di route (untuk 1–2 route spesifik):
// Tambahkan :slug setelah nama parameter
Route::get('/posts/{post:slug}', [PostController::class, 'show']);
Laravel akan menjalankan Post::where('slug', $value)->firstOrFail() — bukan pakai id.
Cara 2 — Override di model (kalau semua route pakai slug):
📁 app/Models/Post.php
class Post extends Model
{
// Bilang ke Laravel: "Kalau resolve Post dari route, selalu pakai field ini"
public function getRouteKeyName(): string
{
return 'slug';
}
}
Lalu di route, tulis seperti biasa tanpa tambahan apapun:
Route::get('/posts/{post}', [PostController::class, 'show']);
📌 Cara 2 lebih rapi kalau semua route untuk model tersebut pakai field yang sama. Cara 1 lebih fleksibel kalau cuma satu-dua route yang butuh berbeda.
⚙️ Explicit Binding — “Kamu yang Kendalikan”
Analoginya
Masih di restoran yang sama. Tapi sekarang kamu bukan pelanggan biasa — kamu adalah manajer yang pasang aturan:
“Setiap kali ada yang sebut ‘Meja 5’, jangan cuma ambil pesanannya. Cek juga apakah mejanya sudah dibayar lunas, dan pastikan statusnya aktif.”
Explicit binding adalah tempat kamu mendaftarkan aturan itu secara global. Setiap kali parameter tertentu muncul di route manapun, logika yang kamu daftarkan akan selalu dijalankan.
Di mana mendaftarkannya
Di AppServiceProvider, tepatnya di method boot().
📁 app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\Route;
use App\Models\Post;
public function boot(): void
{
// Setiap kali {post} muncul di route mana pun:
Route::bind('post', function (string $value) {
// Kamu bebas tulis query apapun di sini
return Post::where('slug', $value)
->where('is_published', true)
->firstOrFail();
});
}
📌 Perhatikan — di sini kamu bisa tambahkan filter apapun. Bukan hanya cari berdasarkan field tertentu, tapi juga cek kondisi tambahan seperti is_published = true. Kalau pakai implicit binding biasa, hal ini tidak bisa dilakukan.
Lalu di route dan controller, kamu tetap tulis seperti biasa:
// routes/web.php
Route::get('/posts/{post}', [PostController::class, 'show']);
// Controller
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
Tidak ada perubahan di route atau controller — yang berubah hanya logika resolving-nya, dan itu tersimpan rapi di satu tempat.
🗺️ Gambaran Besar — Tiga Cara dan Kapan Pakainya
URL: /posts/cara-belajar-laravel
↓
{post} parameter
↓
Bagaimana Laravel resolve?
┌─────────────────────────────────────────────────────────┐
│ 1. Implicit (default) │
│ WHERE id = ? │
│ │
│ 2. Implicit + field kustom │
│ {post:slug} atau getRouteKeyName() │
│ WHERE slug = ? │
│ │
│ 3. Explicit (Route::bind) │
│ Query sepenuhnya kamu tentukan │
│ WHERE slug = ? AND is_published = true │
└─────────────────────────────────────────────────────────┘
🧭 Jadi, Kapan Pakai Yang Mana?
Pakai Implicit (default) ketika: Route pakai id dan tidak ada kondisi filter tambahan. Ini kasus paling umum.
Pakai Implicit + {post:slug} ketika: Hanya 1–2 route spesifik yang butuh field berbeda. Cepat, tidak perlu ubah apapun di tempat lain.
Pakai getRouteKeyName() ketika: Semua route untuk model tersebut selalu pakai field yang sama (misalnya semua /posts/{post} selalu resolve lewat slug). Lebih rapi dari menulis :slug di setiap route.
Pakai Explicit (Route::bind) ketika: Ada logika tambahan yang perlu selalu dijalankan — filter status, soft delete, kondisi khusus. Atau kalau nama parameter di URL berbeda dengan nama model.
📊 Perbandingan Singkat
| Implicit | {param:field} | getRouteKeyName() | Explicit (Route::bind) | |
|---|---|---|---|---|
| Konfigurasi | Otomatis | Di route | Di model | Di Service Provider |
| Scope | Global | Per route | Global per model | Global per parameter |
| Bisa tambah filter | ❌ | ❌ | ❌ | ✅ |
| Perlu ubah controller | ❌ | ❌ | ❌ | ❌ |
| Cocok untuk | Kasus umum | 1–2 route | Satu model konsisten | Query kompleks |
💡 Ingat!
Waktu pertama lihat Post $post di controller tanpa ada findOrFail(), wajar kalau rasanya seperti ada yang hilang.
Tapi justru di situlah intinya — Route Model Binding menghilangkan kode yang tidak perlu kamu lihat, supaya yang tersisa di controller hanya logika yang benar-benar penting.
Satu hal yang perlu diingat: kalau data tidak ditemukan, Laravel otomatis lempar response 404. Ini behavior default yang bisa kamu andalkan — tidak perlu if (!$post) abort(404) lagi.
Dan kalau suatu saat butuh logika yang lebih dari sekadar cari berdasarkan field, Route::bind() ada di sana. Bukan untuk dipakai setiap saat, tapi sangat berharga ketika dibutuhkan.