Skip to content

Commit

Permalink
Completed optimization of allowing multiple types.
Browse files Browse the repository at this point in the history
  • Loading branch information
eneakh committed Sep 19, 2023
1 parent 46abd7e commit 718e7bd
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 32 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,21 @@ class User extends Authenticatable
$user->roles : Illuminate\Database\Eloquent\Collection

// Determine if the user has the given role...
$user->hasRole(string $role) : bool
$user->hasRole($role) : bool

// Access all permissions for a given role belonging to the user...
$user->rolePermissions(string $role) : ?array
$user->rolePermissions($role) : ?array

// Access all permissions belonging to the user...
$user->permissions() : Illuminate\Support\Collection

// Determine if the user role has a given permission...
$user->hasRolePermission(string $role, string $permission) : bool
$user->hasRolePermission($role, $permission) : bool

// Determine if the user has a given permission...
$user->hasPermission(string $permission) : bool
$user->hasPermission($permission) : bool
```
> All method parameters accept string, array, Collection and Enum if you chose to use so.
### Roles & Permissions
Users can receive roles with permissions defined in `App\Providers\LadderServiceProvider` using `Ladder::role` method. This involves specifying a role's slug, name, permissions, and description. For instance, in a blog app, role definitions could be:
Expand Down
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
},
"require-dev": {
"mockery/mockery": "^1.0",
"nunomaduro/collision": "^6.4",
"orchestra/testbench": "^8.0",
"phpunit/phpunit": "^9.0"
},
Expand Down
75 changes: 51 additions & 24 deletions src/HasRoles.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Ladder;

use BackedEnum;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
Expand All @@ -14,50 +15,76 @@ public function roles(): HasMany
return $this->hasMany(UserRole::class, 'user_id');
}

public function hasRole(string $role): bool
public function findRole(UserRole $userRole): ?Role
{
return $this->roles()->where('role', $role)->exists();
return Ladder::findRole($userRole->role);
}

public function findRole(UserRole $userRole): ?Role
public function hasRole(string|array|Collection|BackedEnum $roles): bool
{
return Ladder::findRole($userRole->role);
return $this->filterRoles($roles)->isNotEmpty();
}

public function rolePermissions(string|array|Collection|UserRole|BackedEnum $roles): array
{
return $this->filterRoles($roles)
->map(fn ($role) => (array) optional($this->findRole($role))->permissions)
->flatten()
->unique()
->toArray();
}

public function rolePermissions(string $role): ?array
public function filterRoles(string|array|Collection|UserRole|BackedEnum $roles): Collection
{
$userRole = $this->roles->where('role', $role)->first();
if (is_string($roles)) {
$roles = [$roles];
}

if (!$userRole) {
return [];
if ($roles instanceof BackedEnum) {
$roles = [$roles->value];
}

return $this->findRole($userRole)?->permissions ?: [];
if ($roles instanceof Collection) {
$pivot = $roles->filter(fn ($role) => $role instanceof UserRole);

if ($pivot->isNotEmpty()) {
$roles = $pivot->map(fn ($role) => $role->role);
}
}

return $this->roles->whereIn('role', collect($roles)->filter());
}

public function hasRolePermission(string $role, string $permission): bool
public function hasRolePermission(string|array|Collection|BackedEnum $roles,
string|array|Collection|BackedEnum $permissions): bool
{
$permissions = $this->rolePermissions($role);
if (is_string($permissions)) {
$permissions = [$permissions];
}

if ($permissions instanceof BackedEnum) {
$permissions = [$permissions->value];
}

return in_array($permission, $permissions) ||
in_array('*', $permissions) ||
(Str::endsWith($permission, ':create') && in_array('*:create', $permissions)) ||
(Str::endsWith($permission, ':update') && in_array('*:update', $permissions));
$rolePermissions = collect($this->rolePermissions($roles));

$permissions = collect($permissions);

return $permissions->contains(fn ($permission) =>
$rolePermissions->contains($permission) ||
$rolePermissions->contains('*') ||
(Str::endsWith($permission, ':create') && $rolePermissions->contains('*:create')) ||
(Str::endsWith($permission, ':update') && $rolePermissions->contains('*:update'))
);
}

public function hasPermission(string $permission): bool
public function hasPermission(string|array|Collection|BackedEnum $permissions): bool
{
return $this->roles
->map(fn ($userRole) => $this->hasRolePermission($userRole->role, $permission))
->containsStrict(true);
return $this->hasRolePermission($this->roles, $permissions);
}

public function permissions(): Collection
{
return $this->roles
->map(fn ($role) => $this->rolePermissions($role->attributes['role']))
->flatten()
->unique();
return collect($this->rolePermissions($this->roles));
}

}
100 changes: 97 additions & 3 deletions tests/HasRolesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Foundation\Testing\RefreshDatabase;
use Ladder\Ladder;
use Ladder\Role;
use Tests\TestEnums\Roles;

class HasRolesTest extends OrchestraTestCase
{
Expand Down Expand Up @@ -37,7 +38,7 @@ public function test_user_returns_the_matching_role()
$this->assertSame('admin', $role->key);
}

public function test_rolePermissions_returns_permissions_for_the_users_role()
public function test_rolePermissions_returns_permissions_for_the_users_role_when_string_is_given()
{
Ladder::role('admin', 'Admin', [
'read',
Expand All @@ -51,6 +52,84 @@ public function test_rolePermissions_returns_permissions_for_the_users_role()
$this->assertSame(['read', 'create'], $user->rolePermissions('admin'));
}

public function test_rolePermissions_returns_permissions_for_the_users_role_when_enum_is_given()
{
Ladder::role('admin', 'Admin', [
'read',
'create',
])->description('Some admin description');

$user = User::factory()
->has(UserRole::factory(['role' => 'admin']), 'roles')
->create();

$this->assertSame(['read', 'create'], $user->rolePermissions(Roles::ADMIN));
}

public function test_rolePermissions_returns_permissions_for_the_users_role_when_array_is_given()
{
Ladder::role('admin', 'Admin', [
'read',
'create',
])->description('Some admin description');

Ladder::role('user', 'User', [
'read:post',
'create:post',
])->description('Some admin description');


$user = User::factory()
->has(UserRole::factory(['role' => 'admin']), 'roles')
->has(UserRole::factory(['role' => 'user']), 'roles')
->create();

$this->assertSame(['read', 'create', 'read:post', 'create:post'], $user->rolePermissions(['admin', 'user']));
}

public function test_rolePermissions_returns_permissions_for_the_users_role_when_collection_is_given()
{
Ladder::role('admin', 'Admin', [
'read',
'create',
])->description('Some admin description');

Ladder::role('user', 'User', [
'read:post',
'create:post',
])->description('Some admin description');


$user = User::factory()
->has(UserRole::factory(['role' => 'admin']), 'roles')
->has(UserRole::factory(['role' => 'user']), 'roles')
->create();

$this->assertSame(['read', 'create', 'read:post', 'create:post'], $user->rolePermissions(
collect(['admin', 'user'])
));
}

public function test_rolePermissions_returns_permissions_for_the_users_role_when_eloquent_collection_is_given()
{
Ladder::role('admin', 'Admin', [
'read',
'create',
])->description('Some admin description');

Ladder::role('user', 'User', [
'read:post',
'create:post',
])->description('Some admin description');

$user = User::factory()
->has(UserRole::factory(['role' => 'admin']), 'roles')
->has(UserRole::factory(['role' => 'user']), 'roles')
->create();

$this->assertSame(['read', 'create', 'read:post', 'create:post'], $user->rolePermissions($user->roles));
}

public function test_rolePermissions_returns_empty_permissions_for_members_without_a_defined_role()
{
Ladder::role('admin', 'Admin', [
Expand Down Expand Up @@ -100,6 +179,23 @@ public function test_hasPermission_returns_true_when_checked_against_any_permiss
$this->assertTrue($user->hasPermission('create'));
}

public function test_hasRolePermission_returns_true_when_checked_against_any_prefix_permission()
{
Ladder::role('admin', 'Admin', [
'*:create',
'*:update',
])->description('Some admin description');

$user = User::factory()
->has(UserRole::factory(['role' => 'admin']), 'roles')
->create();

$this->assertTrue($user->hasPermission([
'post:create',
'post:update',
]));
}

public function test_hasPermission_returns_true_when_checked_against_prefixed_permission()
{
Ladder::role('admin', 'Admin', [
Expand Down Expand Up @@ -140,6 +236,4 @@ public function test_permissions_returns_array_of_assigned_permissions()
$user->permissions()->toArray(),
);
}


}
9 changes: 9 additions & 0 deletions tests/TestEnums/Roles.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Tests\TestEnums;

enum Roles: string // This enum is only for test purposes...
{
case ADMIN = 'admin';
case USER = 'user';
}

0 comments on commit 718e7bd

Please sign in to comment.