In Laravel, what is the difference between a gate defined with Gate::define and a policy class, and when would you choose each?
answer
- closure versus class
- defined in AppServiceProvider::boot()
- one-off, not tied to a model
- policy methods named after abilities
- resolved by the model you pass
basics
~20 sA gate is a named closure registered with Gate::define for one-off checks not tied to a model. A policy is a class grouping one model's abilities (view, update, delete), found from the model you pass; most model rules belong there.
solid answer
~40 sBoth answer "may this user do X?" through the same `Gate` service, so every caller — `Gate::allows`, `Gate::authorize`, the `can` middleware, `@can`, `$user->can()` — works with either. A **gate** is a closure (or `[Class, 'method']`) registered by name, usually in `AppServiceProvider::boot()`: `Gate::define('publish-issue', fn (User $user) => $user->is_admin)`. A **policy** is a class such as `ArticlePolicy` whose method names are the abilities (`view`, `update`, `delete`); Laravel picks it from the **model or class passed** as the first argument. The docs call gates a way to learn the basics and recommend policies for robust apps. Rule of thumb: an action on a model — editing an article — goes in that model's policy; a global action with no model, like opening the admin dashboard, fits a gate.
code
php · 16 lines<?php
namespace App\Providers;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
// Global action, no model involved: a gate
Gate::define('view-billing', fn (User $user) => $user->role === 'admin');
}
}go deeper
Define each: a gate is a named closure, a policy is a class of abilities for one model, and both are checked through the Gate.
Explain how the Gate picks a policy from the argument before falling back to a gate, and why an unmatched ability denies.
Organise rules so model actions live in policies and global actions in a few gates, keeping the provider short and testable.
Set the team convention for where authorization rules live, so reviewers can find every rule for a model in one place.
## One authorization service, two ways to register rules Laravel's authorization layer is the `Illuminate\Auth\Access\Gate` service behind the `Gate` facade. Every check — `Gate::allows()`, `Gate::authorize()`, `$user->can()`, the `can` middleware, `#[Authorize]`, `@can` — goes through it. What differs is **where the rule lives**. ## Gates A **gate** is a named callback: ```php Gate::define('open-editorial-dashboard', fn (User $user) => $user->role === 'admin'); Gate::define('publish-article', [ArticlePolicy::class, 'publish']); ``` - Registered in `AppServiceProvider::boot()`. - The first parameter is always the current user; Laravel supplies it, and callers pass only the extra arguments. - Good for **global** actions not tied to a model: viewing a dashboard, exporting a report, toggling site maintenance. - Every gate lives in one provider, which becomes a long list as an app grows. ## Policies A **policy** is a class that groups a model's abilities: ```php class ArticlePolicy { public function update(User $user, Article $article): bool { return $user->id === $article->author_id || $user->role === 'editor'; } } ``` - Generated with `php artisan make:policy ArticlePolicy --model=Article`. - Method names **are** the ability names; `Gate::authorize('update', $article)` calls `update()`. - Laravel finds the policy from the argument: an `Article` instance or the class name `Article::class`. - Resolved through the service container, so constructor injection works. - Can hold a `before()` filter for the whole class. ## How the Gate chooses When you call `Gate::allows('update', $article)`: 1. global `Gate::before` callbacks run first; 2. if the first argument maps to a policy **and** that policy has a method for the ability, the policy's `before()` and then the method run; 3. otherwise a gate defined under that ability name runs; 4. if nothing matches, the result is `null`, which is treated as **deny**. So a policy method wins over a gate with the same name when a model is passed. ## Choosing, on a publishing platform | Rule | Where | |---|---| | An editor may update any article; a writer only their own | `ArticlePolicy::update` | | A reviewer may comment on articles assigned to them | `ArticlePolicy::comment` | | Only admins may open the billing screen | `Gate::define('view-billing', ...)` | | Admins may do everything | `Gate::before` or a policy `before()` | ## Common confusions - Gates and policies are not two security systems; they are two registration styles for one. - A policy does not need registering by hand when names follow the conventions (`App\Models\Article` → `App\Policies\ArticlePolicy`). - Defining a gate called `update` does not make it apply to articles when an `ArticlePolicy::update` exists — the policy is consulted first. - Neither runs by itself: something (a call, middleware, attribute or directive) must invoke the check. ## Growing from gates to policies Many apps start with a handful of gates and move to policies as models multiply. A clean migration: 1. Generate the policy: `php artisan make:policy ArticlePolicy --model=Article`. 2. Move each model-specific gate body into the matching method (`update-article` → `update`). 3. Change callers from `Gate::allows('update-article', $article)` to `Gate::allows('update', $article)`; the ability names become the method names. 4. Delete the old gates so there is one source of truth. 5. Keep genuinely global abilities (`view-billing`, `open-editorial-dashboard`) as gates. A halfway step is `Gate::define('update-article', [ArticlePolicy::class, 'update'])`, which points an old gate name at a policy method. ## What interviewers listen for - That both styles share the `Gate` and its `before`/`after` hooks. - That policies are found from the argument, so the model (or `Article::class`) must be passed. - That the documentation itself steers robust applications towards policies. - That an undefined ability denies rather than allows.
- If both Gate::define('update', ...) and ArticlePolicy::update exist, which runs for Gate::allows('update', $article)?The policy. When the first argument maps to a policy that has an `update` method, the Gate resolves the policy callback before looking at gates registered under that name. Without a model argument, or if the policy lacks the method, the gate runs.
- How does a gate or policy method get the current user?The Gate resolves the currently authenticated user through its user resolver and passes it as the first argument. Callers never pass the user; to check another user, use `Gate::forUser($user)` or `$otherUser->can(...)`.
A gate is a single rule on a sign by the door; a policy is the rulebook kept for one kind of room. The same doorman reads both, and when you name a room he opens its rulebook before looking at the signs.
saying these in an interview costs you the question
- Gates and policies are two separate authorization systems
- You must pass the current user to Gate::allows
- Every policy has to be registered manually in a provider
- A gate with the same name overrides the model's policy method
- Policies run automatically on every model query