Kalau kamu baru masuk ke dunia authorization di Laravel, dua hal ini pasti bikin bingung — bukan karena susah, tapi karena sekilas keliatannya sama. Keduanya soal “boleh atau tidak boleh”. Keduanya ada di ekosistem yang sama. Tapi ternyata mereka punya tempat yang berbeda.
Artikel ini mulai dari analogi dulu. Setelah ngerti kerangka berpikirnya, baru kita masuk ke kode.
🤔 Masalah yang Mereka Selesaikan
Bayangin kamu lagi bangun aplikasi blog. Di aplikasi ini ada dua skenario otorisasi yang berbeda:
- “Apakah user ini boleh masuk ke halaman dashboard admin?”
- “Apakah user ini boleh mengedit post ini?”
Sekilas keduanya tampak sama — dua-duanya soal izin. Tapi coba perhatikan lagi.
Pertanyaan pertama tidak peduli soal objek tertentu. Tidak ada “post yang mana” atau “user yang mana”. Cukup tanya satu hal: apakah kamu admin?
Pertanyaan kedua tidak bisa dijawab tanpa tahu post mana yang dimaksud. Boleh atau tidaknya tergantung pada apakah post itu miliknya sendiri.
Nah, inilah inti perbedaan Gate dan Policy.
Gate dirancang untuk pertanyaan pertama — aturan global yang tidak terikat objek tertentu.
Policy dirancang untuk pertanyaan kedua — aturan yang selalu melibatkan model spesifik.
🚦 Gate — “Peraturan Umum”
Analoginya
Bayangin Gate seperti papan peraturan di pintu masuk gedung kantor.
Di sana tertulis: “Hanya karyawan dengan ID badge merah yang boleh masuk ke lantai 10.”
Peraturan ini berlaku untuk siapapun yang mau masuk. Tidak peduli kamu mau ngapain di sana, tidak peduli kamu mau ketemu siapa. Satu aturan, berlaku di mana saja dan kapan saja.
Gate di Laravel persis seperti itu — dia mendefinisikan aturan izin yang sifatnya global, bukan terikat pada satu objek model tertentu.
Aturan Gate
- Didefinisikan pakai
Gate::define(), biasanya diAuthServiceProvider::boot(). - Isinya closure — fungsi yang menerima
$userdan mengembalikantrueataufalse. - Bisa dipanggil dari mana saja: controller, blade, route, service.
- Cocok untuk aturan yang tidak perlu tahu objek spesifiknya — cukup cek properti user.
Kodenya
📁 app/Providers/AuthServiceProvider.php
Semua
Gate::define()idealnya didaftarkan di sini, di dalam methodboot().
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
public function boot(): void
{
// Aturannya sederhana: hanya admin yang boleh
Gate::define('view-dashboard', fn ($user) => $user->is_admin);
// Bisa juga didefinisikan dengan closure panjang untuk logika lebih kompleks
Gate::define('manage-users', function ($user) {
return $user->is_admin && $user->is_active;
});
}
}
📌 Perhatikan — Gate::define() hanya menerima nama aturan (string) dan closure. Tidak ada model yang terlibat. Itu yang membuat Gate cocok untuk aturan global.
Cara Memakainya
✅ Di Controller (cara langsung):
use Illuminate\Support\Facades\Gate;
public function index()
{
if (! Gate::allows('view-dashboard')) {
abort(403);
}
return view('dashboard');
}
✅ Di Controller (cara rapi — otomatis throw 403):
public function index()
{
$this->authorize('view-dashboard');
return view('dashboard');
}
✅ Di Controller (kalau perlu handle sendiri):
public function index()
{
if (Gate::denies('view-dashboard')) {
return redirect('/')->with('error', 'Akses ditolak.');
}
return view('dashboard');
}
✅ Di Blade (buat sembunyikan elemen):
@can('view-dashboard')
<a href="/dashboard">Dashboard</a>
@endcan
✅ Di Route (paling ringkas):
Route::get('/dashboard', DashboardController::class)
->middleware('can:view-dashboard');
🗂️ Policy — “Aturan Per Model”
Analoginya
Sekarang bayangin Policy seperti peraturan kepemilikan barang di gudang.
Setiap barang punya label nama pemiliknya. Peraturannya jelas: “Hanya pemilik barang yang boleh mengambil atau memindahkannya.”
Tapi untuk menjalankan peraturan ini, kamu tidak bisa hanya tanya “kamu siapa?”. Kamu juga harus tahu “barang mana yang kamu mau ambil?”. Dua informasi itu selalu dibutuhkan bersamaan.
Policy di Laravel persis seperti itu — dia selalu bekerja dengan dua informasi: siapa user-nya dan objek model mana yang sedang diakses.
Aturan Policy
- Satu Policy = satu Model.
PostPolicyuntuk modelPost,CommentPolicyuntuk modelComment. - Dibuat sebagai class, bukan closure.
- Method-nya menerima
$userdan instance model sebagai parameter. - Laravel otomatis menemukannya (auto-discovery) atau bisa didaftarkan manual di
AuthServiceProvider.
Membuatnya
php artisan make:policy PostPolicy --model=Post
📁 app/Policies/PostPolicy.php
Disimpan di folder
Policies. Konvensi penamaan: nama model +Policy.
<?php
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
class PostPolicy
{
// Boleh lihat semua post? (tidak butuh objek post spesifik)
public function viewAny(User $user): bool
{
return true; // semua user boleh
}
// Boleh lihat post ini?
public function view(User $user, Post $post): bool
{
return $post->is_published || $user->id === $post->user_id;
}
// Boleh buat post baru?
public function create(User $user): bool
{
return $user->is_verified;
}
// Boleh edit post ini? — Harus milik user sendiri
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
// Boleh hapus post ini?
public function delete(User $user, Post $post): bool
{
return $user->id === $post->user_id || $user->is_admin;
}
}
📌 Perhatikan — setiap method yang butuh mengecek objek spesifik (view, update, delete) selalu menerima $post sebagai parameter kedua. Itulah yang membedakan Policy dari Gate: dia selalu tahu “ini post yang mana”.
Cara Memakainya
✅ Di Controller:
public function edit(Post $post)
{
// Laravel otomatis ambil PostPolicy dan cek method 'update'
$this->authorize('update', $post);
return view('posts.edit', compact('post'));
}
✅ Di Blade:
@can('update', $post)
<a href="/posts/{{ $post->id }}/edit">Edit</a>
@endcan
✅ Di Route:
Route::put('/posts/{post}', [PostController::class, 'update'])
->middleware('can:update,post');
🗺️ Gambaran Besar — Posisi Tiap Komponen
Ini gambaran keseluruhan struktur dan hubungannya:
app/
├── Providers/
│ └── AuthServiceProvider.php ← Tempat daftar Gate
│
├── Policies/
│ ├── PostPolicy.php ← Policy untuk model Post
│ ├── CommentPolicy.php ← Policy untuk model Comment
│ └── UserPolicy.php ← Policy untuk model User
Cara kerja saat $this->authorize('update', $post) dipanggil:
Controller memanggil $this->authorize('update', $post)
↓
Laravel ambil user yang login
↓
Laravel deteksi: $post adalah instance Post → cari PostPolicy
↓
Jalankan PostPolicy::update($user, $post)
↓
true → lanjut | false → throw 403 AuthorizationException
🧭 Jadi, Kapan Pakai Yang Mana?
Pakai Gate ketika: Aturannya tidak butuh tahu objek spesifik — cukup cek properti user. Misalnya: “apakah user ini admin?”, “apakah user ini sudah verifikasi email?”, “apakah user ini punya plan premium?”
Pakai Policy ketika: Aturannya bergantung pada objek model tertentu yang sedang diakses. Misalnya: “apakah post ini milik user ini?”, “apakah komentar ini boleh dihapus user ini?”
Pakai keduanya (paling umum di Laravel) ketika: Gate untuk akses level tinggi (siapa yang boleh masuk ke fitur apa), Policy untuk akses level objek (siapa yang boleh melakukan apa ke objek tertentu).
📊 Perbandingan Singkat
| Gate | Policy | |
|---|---|---|
| Bentuknya | Closure | Class |
| Terikat model | ❌ | ✅ |
| Butuh objek spesifik | ❌ | ✅ |
| Cocok untuk | Aturan global | Aturan per-model |
| Tempat daftar | AuthServiceProvider | app/Policies/ |
| Contoh penggunaan | can:view-dashboard | can:update,post |
💡 Ingat!
Waktu pertama kali belajar ini, wajar kalau masih bingung dan ngerasa “kenapa tidak pakai Gate saja untuk semuanya?”
Boleh saja — Gate bisa menerima model sebagai parameter. Tapi begitu punya banyak aturan untuk satu model, semuanya akan menumpuk di AuthServiceProvider dan jadi berantakan.
Policy memisahkan aturan per model ke file terpisah. Ini bukan tentang benar atau salah — ini tentang kode yang mudah ditemukan dan dirawat saat aplikasi makin besar.
Analoginya: Gate itu papan pengumuman di lobi. Policy itu buku peraturan yang berbeda untuk tiap departemen. Keduanya perlu ada, dan keduanya punya peran masing-masing.