skip to content

In Laravel, what is the difference between a gate defined with Gate::define and a policy class, and when would you choose each?

level: juniorimportance: must knowfreq 72%

answer

  1. closure versus class
  2. defined in AppServiceProvider::boot()
  3. one-off, not tied to a model
  4. policy methods named after abilities
  5. resolved by the model you pass

basics

~20 s

A 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 s

Both 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
<?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

for a junior

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.

for a middle

Explain how the Gate picks a policy from the argument before falling back to a gate, and why an unmatched ability denies.

for a senior

Organise rules so model actions live in policies and global actions in a few gates, keeping the provider short and testable.

for a principal

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