Laravel's UsePolicy Attribute: Explicit Policy Control

Laravel's UsePolicy Attribute: Explicit Policy Control

Ever needed to use a different policy than Laravel's naming convention suggests, or wanted to make policy assignments crystal clear in your codebase? The UsePolicy attribute solves both challenges elegantly.

Convention vs. Explicit Declaration

Laravel's policy auto-discovery follows a simple naming convention: a Post model automatically maps to a PostPolicy class. This works perfectly most of the time, but there are scenarios where you need more control:

// Traditional auto-discovery (implicit)
class Post extends Model
{
    // 
}

// With explicit policy declaration
#[UsePolicy(PostPolicy::class)]
class Post extends Model
{
    // 
}

The UsePolicy attribute makes the policy relationship explicit in your model definition, eliminating any guesswork about which policy handles authorization.

Real-World Example

Consider a content management system where different types of posts require different authorization rules. You might have a general Post model but need specialized policies for different content types:

<?php

namespace App\Models;

use App\Policies\AdminContentPolicy;
use Illuminate\Database\Eloquent\Model;
use Laravel\Framework\Auth\Access\UsePolicy;

#[UsePolicy(AdminContentPolicy::class)]
class Post extends Model
{
    protected $fillable = [
        'title', 'content', 'status', 'content_type'
    ];

    public function isAdminContent(): bool
    {
        return in_array($this->content_type, [
            'announcement', 'policy_update', 'system_notice'
        ]);
    }
}

class AdminContentPolicy
{
    public function view(User $user, Post $post): bool
    {
        if ($post->isAdminContent()) {
            return $user->hasRole('admin') || $user->hasRole('moderator');
        }
        
        return $post->status === 'published';
    }

    public function update(User $user, Post $post): bool
    {
        if ($post->isAdminContent()) {
            return $user->hasRole('admin');
        }
        
        return $user->id === $post->user_id || $user->hasRole('editor');
    }

    public function delete(User $user, Post $post): bool
    {
        return $user->hasRole('admin') || 
               ($user->id === $post->user_id && !$post->isAdminContent());
    }
}

class PostController extends Controller
{
    public function update(Request $request, Post $post)
    {
        $this->authorize('update', $post); // Uses AdminContentPolicy
        
        $post->update($request->validated());
        
        return redirect()->route('posts.show', $post);
    }
}

This approach is particularly valuable when you have models that share names across different contexts (like Post models in both blog and forum modules), when you're using shared policies across multiple models, or when you want to make authorization relationships immediately obvious to new team members.

The attribute also helps with IDE support and static analysis tools, making it easier to trace authorization flows and understand security implementations at a glance. No more hunting through file naming conventions to understand which policy applies to which model.

Stay Updated with More Laravel Tips

Enjoyed this article? There's plenty more where that came from! Subscribe to our channels to stay updated with the latest Laravel tips, tricks, and best practices:

Subscribe to Harris Raftopoulos

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe