Gate adalah bentuk otorisasi paling ringkas di Laravel: satu closure, satu nama ability, terdaftar sekali, bisa dipakai di mana saja. Ketika aturan akses kita masih sependek “hanya admin yang boleh membuka pengaturan”, mendirikan class khusus jelas berlebihan — cukup satu baris Gate::define di service provider. Pada chapter ini kita mendefinisikan gate sederhana dan gate berparameter model, lalu memanggilnya dari berbagai arah: boolean untuk branching, exception untuk menolak request, middleware untuk route, dan directive @can untuk Blade. Semua contoh dijalankan nyata sehingga kita bisa melihat angka 403 muncul persis di tempat yang diprediksi.
Mendefinisikan Gate di AppServiceProvider #
Kita daftarkan dua gate di method boot() milik AppServiceProvider:
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
Gate::define('edit-settings', function (User $user): bool {
return $user->role === 'admin';
});
Gate::define('touch-post', function (User $user, Post $post): bool {
return $post->user_id === $user->id;
});Kedua closure menerima User $user yang otomatis diisi pengguna yang sedang login. Gate pertama tidak butuh data lain — ia hanya membaca kolom role. Gate kedua menerima parameter tambahan $post, sehingga pertanyaannya berubah menjadi spesifik: “bolehkah pengguna ini menyentuh tulisan ini?”. Nama ability bebas kita pilih, tetapi gaya kebab-case seperti edit-settings adalah konvensi yang umum.
Memeriksa Gate: allows dan denies #
Method Gate::allows() mengembalikan true bila diizinkan, sedangkan Gate::denies() adalah kebalikannya. Karena contoh berikut dijalankan dari CLI tanpa pengguna yang login, kita memakai Gate::forUser($user) untuk mengikat pemeriksaan pada user tertentu:
$budi = User::create([
'name' => 'Budi', 'email' => '[email protected]',
'password' => 'password-rahasia', 'role' => 'penulis',
]);
$admin = User::create([
'name' => 'Admin', 'email' => '[email protected]',
'password' => 'password-rahasia', 'role' => 'admin',
]);
$post = Post::create([
'user_id' => $budi->id,
'judul' => 'Belajar Gate Laravel',
'isi' => 'Isi tulisan tentang gate.',
]);
echo 'budi edit-settings : allows='
.var_export(Gate::forUser($budi)->allows('edit-settings'), true)
.' denies='.var_export(Gate::forUser($budi)->denies('edit-settings'), true)."\n";
echo 'admin edit-settings : allows='
.var_export(Gate::forUser($admin)->allows('edit-settings'), true)."\n";
echo 'budi touch-post : allows='
.var_export(Gate::forUser($budi)->allows('touch-post', $post), true)."\n";
echo 'admin touch-post : allows='
.var_export(Gate::forUser($admin)->allows('touch-post', $post), true)."\n";Output yang Dihasilkan:
budi edit-settings : allows=false denies=true
admin edit-settings : allows=true
budi touch-post : allows=true
admin touch-post : allows=falseHasilnya membentuk salib yang rapi: Budi gagal di edit-settings karena rolenya penulis, tetapi lolos di touch-post karena tulisannya memang milik Budi. Admin berlaku sebaliknya. Dua boolean ini biasa dipakai untuk branching tampilan atau logika di controller.
Gate::authorize: Menolak dengan 403 #
Gate::authorize() mengambil jalan yang lebih tegas — ketika closure mengembalikan false, method ini melempar AuthorizationException yang oleh framework diterjemahkan menjadi respons 403. Kita buat controller kecil:
use Illuminate\Support\Facades\Gate;
class GateController extends Controller
{
public function paksa(): JsonResponse
{
Gate::authorize('edit-settings');
return response()->json(['status' => 'ok']);
}
}Baris Gate::authorize('edit-settings') menjadi gerbang: hanya admin yang mencapai baris return di bawahnya. Feature test berikut membuktikan efeknya pada dua peran:
public function test_gate_authorize_melempar_403(): void
{
$budi = User::factory()->create([
'name' => 'Budi', 'email' => '[email protected]', 'role' => 'penulis',
]);
$response = $this->actingAs($budi)->get('/gate/paksa');
echo 'penulis GET /gate/paksa status: '.$response->status()."\n";
$response->assertForbidden();
}Output yang Dihasilkan:
penulis GET /gate/paksa status: 403Gate::any dan Gate::check #
Terkadang satu keputusan bergantung pada beberapa ability sekaligus. Gate::any() lolos bila salah satu ability terpenuhi, sedangkan Gate::check() menuntut semuanya terpenuhi:
$super = User::create([
'name' => 'Super Admin', 'email' => '[email protected]',
'password' => 'password-rahasia', 'role' => 'super-admin',
]);
$abilitas = ['edit-settings', 'touch-post'];
echo 'budi any='.var_export(Gate::forUser($budi)->any($abilitas, $post), true)
.' check='.var_export(Gate::forUser($budi)->check($abilitas, $post), true)."\n";
echo 'admin any='.var_export(Gate::forUser($admin)->any($abilitas, $post), true)
.' check='.var_export(Gate::forUser($admin)->check($abilitas, $post), true)."\n";
echo 'super any='.var_export(Gate::forUser($super)->any($abilitas, $post), true)
.' check='.var_export(Gate::forUser($super)->check($abilitas, $post), true)."\n";Output yang Dihasilkan:
budi any=true check=false
admin any=true check=false
super any=true check=trueBudi hanya menguasai touch-post dan admin hanya menguasai edit-settings, jadi any() mengembalikan true untuk keduanya sementara check() mengembalikan false. Hanya pengguna super yang menuntaskan kedua ability — dan itu bukan kebetulan, seperti kita lihat berikutnya.
Interception dengan Gate::before #
Gate::before() mendaftarkan callback yang berjalan sebelum semua pemeriksaan lain. Ini tempat sempurna untuk super admin:
Gate::before(function (User $user, string $ability): bool|null {
return $user->role === 'super-admin' ? true : null;
});Mengembalikan true berarti lolos apa pun ability-nya; mengembalikan null berarti “tidak berpendapat, lanjutkan pemeriksaan normal”. Itulah sebabnya pengguna super pada output sebelumnya menembus check() tanpa mendefinisikan ability apa pun — closure before sudah memutuskan lebih dulu.
Gate di Middleware dan Blade #
Karena middleware can: dan directive @can sama-sama bertanya ke Gate, satu definisi gate otomatis aktif di kedua tempat. Di route:
use Illuminate\Support\Facades\Route;
Route::get('/gate/rahasia', fn () => response()->json(['rahasia' => 'kunci-api-123']))
->middleware('can:edit-settings')
->name('gate.rahasia');Feature test membandingkan dua peran pada route yang sama:
$budi = User::factory()->create([
'name' => 'Budi', 'email' => '[email protected]', 'role' => 'penulis',
]);
$admin = User::factory()->create([
'name' => 'Admin', 'email' => '[email protected]', 'role' => 'admin',
]);
echo 'penulis GET /gate/rahasia status: '
.$this->actingAs($budi)->get('/gate/rahasia')->status()."\n";
echo 'admin GET /gate/rahasia status: '
.$this->actingAs($admin)->get('/gate/rahasia')->status()."\n";
echo 'body: '.$this->actingAs($admin)->get('/gate/rahasia')->getContent()."\n";Output yang Dihasilkan:
penulis GET /gate/rahasia status: 403
admin GET /gate/rahasia status: 200
body: {"rahasia":"kunci-api-123"}Di Blade, pengecekan yang sama ditulis @can('edit-settings') ... @endcan. Gate terasa ringan untuk satu-dua aturan, tetapi bayangkan dua belas ability yang semuanya berbicara tentang model Post — closure bertumpuk di satu provider mulai sulit dirawat. Untuk wilayah itu Laravel menyediakan class khusus per model, yaitu policy, yang menjadi fokus chapter berikutnya. Rujukan lengkapnya ada di dokumentasi Gates
.