Gate vs Policy di Laravel — Perbedaan dan Cara Pakainya

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:

  1. “Apakah user ini boleh masuk ke halaman dashboard admin?”
  2. “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 di AuthServiceProvider::boot().
  • Isinya closure — fungsi yang menerima $user dan mengembalikan true atau false.
  • 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 method boot().

<?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. PostPolicy untuk model Post, CommentPolicy untuk model Comment.
  • Dibuat sebagai class, bukan closure.
  • Method-nya menerima $user dan 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

GatePolicy
BentuknyaClosureClass
Terikat model
Butuh objek spesifik
Cocok untukAturan globalAturan per-model
Tempat daftarAuthServiceProviderapp/Policies/
Contoh penggunaancan:view-dashboardcan: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.