skip to content

In a Laravel policy, why do viewAny and create receive only the user while view and update also receive a model, and how do you call each?

level: juniorimportance: should knowfreq 45%

answer

  1. no instance exists yet
  2. pass the class name instead
  3. Gate::authorize('viewAny', Article::class)
  4. index maps to viewAny, show to view
  5. extra context in an array

basics

~20 s

viewAny and create decide about a whole kind of model, before any instance exists, so they take only the user; you call them with the class name, Gate::authorize('create', Article::class). view and update judge one record and receive it: Gate::authorize('update', $article).

solid answer

~40 s

Policy methods follow the data the decision needs. `viewAny(User $user)` answers "may this user list articles at all?" and `create(User $user)` "may they write one?" — there is no article to inspect, so the caller passes the **class name** purely to select the policy: `Gate::authorize('viewAny', Article::class)`, `$user->can('create', Article::class)`, `@can('create', App\Models\Article::class)`, `->can('create', Article::class)` on a route. The Gate drops that string before calling the method. `view(User $user, Article $article)` and `update(...)` judge **one record**, so you pass the instance: `Gate::authorize('update', $article)`. The traditional resource mapping is index → `viewAny`, show → `view`, create/store → `create`, edit/update → `update`, destroy → `delete`. Extra context goes in an array: `Gate::authorize('update', [$article, $section])`. `viewAny` does not filter which rows a listing returns.

code

php · 24 lines
php
<?php

namespace App\Http\Controllers;

use App\Models\Article;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;

class ArticleController extends Controller
{
    public function index(Request $request)
    {
        Gate::authorize('viewAny', Article::class);

        // viewAny allows the page; the query still limits the rows
        return $request->user()->assignedArticles()->paginate();
    }

    public function update(Request $request, Article $article)
    {
        Gate::authorize('update', $article);
        // ...
    }
}

go deeper

for a junior

Remember: viewAny and create take the class name, view, update and delete take the model instance.

for a middle

Explain how the Gate uses a class-name string only to select the policy, and how the can middleware turns a route parameter into a model.

for a senior

Pair viewAny with a scoped query so an allowed index never leaks rows, and pass extra context through the argument array.

for a principal

Keep the policy vocabulary consistent across models so every team maps index, show and mutations to the same abilities.

## Two kinds of question A policy answers two different kinds of authorization question: - **Class-level**: may this user list articles, or create one? No particular article is involved — at `create` time it does not even exist. - **Instance-level**: may this user view, update or delete *this* article? The answer depends on the record: its author, status, or section. Laravel's policy methods mirror that split. ## The generated methods `php artisan make:policy ArticlePolicy --model=Article` writes: | Method | Signature | Question | |---|---|---| | `viewAny` | `(User $user)` | may the user see the article index at all? | | `view` | `(User $user, Article $article)` | may the user see this article? | | `create` | `(User $user)` | may the user create articles? | | `update` | `(User $user, Article $article)` | may the user edit this article? | | `delete` | `(User $user, Article $article)` | may the user delete this article? | | `restore`, `forceDelete` | `(User $user, Article $article)` | soft-delete lifecycle | ## Calling class-level methods Because there is no instance, you pass the **class name** so the Gate can find the policy: ```php Gate::authorize('viewAny', Article::class); $request->user()->can('create', Article::class); Route::post('/articles', [ArticleController::class, 'store'])->can('create', Article::class); ``` In Blade: `@can('create', App\Models\Article::class)`. Internally, when the first argument is a string, the Gate uses it to select the policy and then removes it before calling the method, so `create()` receives only the user. ## Calling instance-level methods ```php Gate::authorize('update', $article); Route::put('/articles/{article}', ...)->can('update', 'article'); ``` With the `can` middleware or `#[Authorize('update', 'article')]`, the second argument is a **route parameter name**; route-model binding has already turned it into an `Article` because `SubstituteBindings` has higher middleware priority than `Authorize`. ## Extra context Pass an array: the first element selects the policy, the rest become extra method parameters. ```php Gate::authorize('update', [$article, $request->section]); // ArticlePolicy::update(User $user, Article $article, Section $section) ``` ## Resource-controller mapping The traditional mapping (from the `AuthorizesRequests` trait's resource ability map) is: 1. `index` → `viewAny` 2. `show` → `view` 3. `create`, `store` → `create` 4. `edit`, `update` → `update` 5. `destroy` → `delete` In Laravel 13 the skeleton's base controller no longer includes that trait, so you usually write these checks with `Gate::authorize()` or `#[Authorize]` per action. ## What `viewAny` does not do `viewAny` returns one boolean for the whole index. It does **not** decide which rows appear: a reviewer allowed to see the index still must only see articles assigned to them, and that is a query-scoping job. Returning `true` from `viewAny` and forgetting to scope the query is a common way to leak drafts. ## Guests on public pages Published articles can be read anonymously. A policy method only runs for a guest if its user parameter is nullable: ```php public function view(?User $user, Article $article): bool { return $article->published_at !== null || $user?->id === $article->author_id; } ``` With a non-nullable `User $user`, the Gate skips the method for guests and the check is denied. ## Common mistakes 1. Calling `Gate::authorize('create')` with no class name, so the policy is never reached. 2. Passing an unsaved `new Article` to `create`: it reaches the policy, but the method signature says it expects no model, which confuses readers. 3. Treating `viewAny` as a row filter. 4. Forgetting `restore` and `forceDelete` on models that use soft deletes; the generated stub denies both until written.

  • What goes wrong if you call Gate::authorize('create') without Article::class?
    With no argument there is nothing to map to a policy, so the Gate looks for a plain gate named `create`. If none exists the result is null and the check is denied, even though `ArticlePolicy::create()` would have allowed it.
  • Why is a route-parameter name, not a class, passed to can:update,article?
    The `can` middleware looks the name up with `$request->route('article')`, which route-model binding has already resolved to an `Article` instance. A value containing a backslash is instead treated as a class name, which is how `can:create,App\Models\Article` selects the policy without an instance.

viewAny is the question at the library entrance — may you browse the stacks at all? view is the question at a locked cabinet — may you open this particular book?

saying these in an interview costs you the question

  • viewAny filters which records the index query returns
  • create needs a new, unsaved model instance to be passed
  • Passing only the ability name reaches the policy automatically
  • view and viewAny are interchangeable names
  • The Gate passes the class-name string into create()