$ lexprog.com

// notes from an old coder -- php, databases, and the occasional rant

[March 07, 2026] Laravel

Laravel Policies and Gates

Laravel Policies and Gates: Tips & Tricks

────────────────────────────────────────────────────────

Laravel Policies and Gates: Tips & Tricks

Tip: Use before() for Super Admin

public function before(User $user, string $ability): ?bool
{
    if ($user->isAdmin()) {
        return true;
    }
    return null; // Let the policy decide
}

Returning null means "continue to the actual policy method."

Gotcha: Gates vs Policies

Gates are closures for simple checks. Policies are classes for model-specific checks. Use policies when you have a model.

Tip: Use @canany in Blade

@canany(['update', 'delete'], $post)
    <a href="/posts/{{ $post->id }}/edit">Manage</a>
@endcanany

Tip: Policy Methods Don't Need to Match CRUD

You can add any method:

public function publish(User $user, Post $post): bool
{
    return $user->canPublish() && $post->isDraft();
}

Gotcha: authorize() Throws 403

$this->authorize('update', $post);
// Throws AuthorizationException (403) if denied

Use $this->can() if you want a boolean instead.

Tip: Resource Policies

Generate a policy with all CRUD methods:

php artisan make:policy PostPolicy --model=Post

Tip: Use Gate::inspect() for Detailed Results

$result = Gate::inspect('view', $post);

if ($result->allowed()) {
    // ...
} else {
    echo $result->message();
}

Gotcha: Policy Discovery

Laravel auto-discovers policies if they follow the naming convention: PostPolicy for Post model. No registration needed.

Tip: Use route:cache Carefully

php artisan route:cache is fast, but it doesn't work with closure-based routes. Every time you cache routes, Laravel serializes them. If you have Route::redirect() or closure callbacks, the cache breaks. Stick to controller-based routes in production.

Tip: Model APP_KEY Rotation

Rotating APP_KEY invalidates all encrypted data — cookies, encrypted DB columns, and password reset tokens. If you must rotate (e.g., after a leak), plan a migration that re-encrypts existing data with the new key.

Gotcha: Local Scope Leaks

Global scopes defined in booted() apply to ALL queries on that model — including relationships. An innocent User::all() in admin panel might exclude soft-deleted users if a global scope is active.

Senior Insight

I've worked on Laravel applications that ran for years without major issues and others that collapsed under modest traffic. The difference almost always comes down to database query discipline. Applications that use explain() on their queries, add indexes proactively, and avoid N+1 patterns in API responses scale effortlessly. Applications that treat the database as an opaque storage layer inevitably hit performance walls. Query profiling should be part of every code review, not an afterthought.

Source: Laravel News (https://laravel-news.com/), Freek.dev (https://freek.dev/tags/laravel), Spatie Blog (https://spatie.be/blog)

────────────────────────────────────────────────────────
<-- back to posts