Support policy responses in filament actions

A short tutorial on how to use Laravel's policy responses in your Filament actions

Lukas Frey Executive director
veröffentlicht vor 2 Tagen

Introduction

The FilamentPHP framework has quite a good support for policies out of the box. All default filament actions automatically check your policies and authorizes the actions correctly.

However sometimes you might need a little bit more control over the authorization's response rather than a simple true/false response. This is where Policy Responses from Laravel come in handy.

As of filament v3, there is no built in support for these though.

In this quick tutorial, I will show you a simple way to add support for policy responses to your delete actions.

To keep things simple, we will showcase it on a Table Delete action, but you can use the same idea for other actions too.

Writing a policy

We need to write a policy for our model which will return a Policy Response. If you know how to do this or already have one created, you can skip this step.

class MyModelPolicy {

    public function delete(User $user, Model $record): bool | Response
    {
        // Return true if allowed

        // ...

        // Here we checked that the delete is not allowed, so we return a Policy Response
        return Response::deny('You are not allowed to delete this record, because it has associations with other models.');
    }

}

Creating a wrapper action

First let's create a wrapper action which will be responsible for checking the policy response:

class ConditionableDeleteAction
{
    use Configurable;

    public function __construct(
        protected MountableAction $action,
    ) {}

    public function configure(): static
    {
        $this->action
            ->authorize('deleteAny')
            ->mountUsing(static function (MountableAction $action, Model $record) {
                $response = Gate::inspect('delete', $record);

                if ($response->allowed()) {
                    return;
                }

                // Only modify the action if the response contains a message - otherwise it might just have been a bool with the `false` value.
                if ($message = $response->message()) {
                    $action
                        ->modalDescription(Markdown::inline($message))
                        ->modalSubmitAction(fn ($action) => $action->hidden())
                    ;
                }
            })
        ;

        return $this;
    }

    public static function for(MountableAction $action): MountableAction
    {
        return self::make($action)->action;
    }

    public static function make(MountableAction $action): static
    {
        return app(static::class, [
            'action' => $action,
        ])
            ->configure()
        ;
    }
}

This action, when mounted, will check whether we are allowed to delete the related record. If not and we have returned a policy response (instead of a simple false boolean), we read the message from the response and display it in the confirmation modal. Additionally, we also hide the delete button.

Using in a table resource

And that's it! Now you can use it in your model resource such as:

public static function table(Table $table): Table
    {
        return $table
            ->columns([
                // ...
            ])
            ->actions([
                // ...
                ConditionableDeleteAction::for(Tables\Actions\DeleteAction::make()),
            ])
        ;
    }

Now whenever you click on the delete action, if deleting is not possible, the reason provided in your policy response will be shown in the confirmation modal.

Artikel teilen

Verfasst von
Lukas Frey Executive director
Kontaktiere mich lukas.frey@guava.cz
Kontaktieren