How to ignore a filter for one policy method

A couple approaches

Joel Clermont
Joel Clermont
2024-06-21

A recently described how I refactored some Blade logic into a policy method.

This tip continues that discussion and shares one little quirk I ran into with the refactor.

Most policy logic takes into account the user that is logged in to decide if they can take an action or not. But layered on top of that, we use the policy's before method to globally allow the highest-privileged user role to bypass the checks.

I created a problem with my refactor though, since this particular policy method was not user-specific. It was enforcing a business rule to not generate a public preview link when that content would not be published to the public site.

So how could I ignore the before filter for this one showPublicLink policy method?

Here's the approach I used:

public function before(User $user, string $ability)
{
    if ($ability === 'showPublicLink') {
        return null;
    }

    if ($user->hasRole(Role::SUPER_ADMIN)) {
        return true;
    }
}

This works, but anytime I see a "magic string", I get a little nervous.

Another idea I had was to decorate the policy method with an attribute and then check for that attribute instead:

#[EnforceForAllUsers]
public function showPublicLink(User $user, Show $show): bool { /* logic omitted */}

public function before(User $user, string $ability)
{
    $policyMethod = new ReflectionMethod(self::class, $ability);
    if ($policyMethod->getAttributes(EnforceForAllUsers::class)) {
        return null;
    }
    
    if ($user->hasRole(Role::SUPER_ADMIN)) {
        return true;
    }
}

This is an attribute I created, not something that exists in Laravel. So the name could be anything, but I chose EnforceForAllUsers to be descriptive.

Then my before method uses reflection to see if the policy method being run has that attribute decorating it, and if so it skips the logic.

I'm not sure if I fully like this approach, but it's an interesting thought experiment.

On the other hand, if this attribute existed in Laravel and I didn't have to do reflection myself, I'd definitely reach for it.

Here to help,

Joel

P.S. What do you think of the attribute approach? Hit reply and let me know how you'd handle it.

Toss a coin in the jar if you found this helpful.
Want a tip like this in your inbox every weekday? Sign up below 👇🏼

Level up your Laravel skills!

Each 2-minute email has real-world advice you can use.