laravel-permission icon indicating copy to clipboard operation
laravel-permission copied to clipboard

Enum as $cast on Permission model

Open LashchenkoV opened this issue 10 months ago • 4 comments

Description

According to the official documentation, Laravel Permission package should support PHP Enums when used as permission names. However, when using a BackedEnum for name, the package fails with the following error:

There is no permission named .. for guard ... Despite the permission existing in the database, the package does not recognize it when using an Enum cast.

Version: 6.x (latest) Environment: PHP: 8.2+ Laravel: 10.x Database: MySQL 8.0+ OS: macOS

Steps To Reproduce

  1. Create an Enum for Permissions model:
enum PermissionName: string
{
    case CAN_VIEW_TEXT = 'can_view_text';
}
  1. Use Enum in a Custom Permission Model:
use Spatie\Permission\Models\Permission;

class CustomPermission extends Permission
{
    protected $casts = [
        'name' => PermissionName::class, // Officially supported method
    ];
}
  1. Set custom class in config permissions.php

'permission' => \App\Containers\CustomPermission::class, 4. Assign permission to existing role

$role = Role::findByName('role_name');
$role->givePermissionTo(PermissionName::CAN_VIEW_TEXT->value);
or 
$role->givePermissionTo(PermissionName::CAN_VIEW_TEXT);

Expected Behavior The package should correctly retrieve the permission from the database. givePermissionTo() should accept an Enum. Actual Behavior The package throws an exception stating that the permission does not exist.

The problem is in PermissionRegistrar file.

    public function getPermissions(array $params = [], bool $onlyOne = false): Collection
    {
        $this->loadPermissions();

        $method = $onlyOne ? 'first' : 'filter';

        $permissions = $this->permissions->$method(static function ($permission) use ($params) {
            foreach ($params as $attr => $value) {

// Here we need to check if($permission->getAttribute($attr) is enum) - call ->value.

                if ($permission->getAttribute($attr) != $value) {
                    return false;
                }
            }

            return true;
        });

        if ($onlyOne) {
            $permissions = new Collection($permissions ? [$permissions] : []);
        }

        return $permissions;
    }

Example Application

No response

Version of spatie/laravel-permission package:

6.13

Version of laravel/framework package:

10.x

PHP version:

8.2

Database engine and version:

No response

OS: Windows/Mac/Linux version:

No response

LashchenkoV avatar Feb 06 '25 13:02 LashchenkoV

We should probably start by expanding the test suite to account for using $casts with enums. That'll make it more clear what dependencies are related to this.

I think "fixing" it will also require an update to the contract signatures, to allow for string|BackedEnum for permission names. That would be a breaking change, so might tag a new major version for merging that. And change min PHP requirement to 8.1+.

drbyte avatar Feb 06 '25 18:02 drbyte

In the meantime you can still use enums without using $casts. That will let the existing functionality simply convert the enums to their string values when encountered.

Docs have been updated to add this workaround.

drbyte avatar Feb 06 '25 19:02 drbyte

You are indeed correct. I still use enum, but without $casts in my model.

It would be beneficial to include a fix for this bug in the next update, if feasible.

LashchenkoV avatar Feb 06 '25 19:02 LashchenkoV

Notes to self:

Ref #2609 Ref #2616 Ref https://github.com/spatie/laravel-permission/blob/main/docs/basic-usage/enums.md

  • Contracts will have to be changed, so targeting v7 for this due to breaking change.
  • Will also drop PHP 8.0 support in v7, for various reasons including that enums aren't supported until PHP 8.1.
  • For thoroughness will probably convert all BackedEnum references to its parent UnitEnum, although our only implementation is with named strings.
  • Would like to rely on Laravel's internal helper function enum_value(), but it's not available in all older versions.

drbyte avatar Mar 02 '25 18:03 drbyte