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?
answer
- no instance exists yet
- pass the class name instead
- Gate::authorize('viewAny', Article::class)
- index maps to viewAny, show to view
- extra context in an array
basics
~20 sviewAny 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 sPolicy 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
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
Remember: viewAny and create take the class name, view, update and delete take the model instance.
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.
Pair viewAny with a scoped query so an allowed index never leaks rows, and pass extra context through the argument array.
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()