Skip to content

Commit

Permalink
Cache (caffeinated#110)
Browse files Browse the repository at this point in the history
* Add experimental cache layer

* Added anyRole() and allRoles() checks
  • Loading branch information
kaidesu authored Jun 28, 2019
1 parent 4cc7e04 commit d60e5b4
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 10 deletions.
38 changes: 38 additions & 0 deletions config/shinobi.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,44 @@

return [

/*
|--------------------------------------------------------------------------
| Experimental Cache
|--------------------------------------------------------------------------
|
| Shinobi ships with an experimental caching layer in an attempt to lessen
| the load on resources when checking and validating permissions. By
| default this is disabled, please enable to provide feedback.
*/

'cache' => [

/**
* You may enable or disable the built in caching system. This is useful
* when debugging your application. If your application already has its
* own caching layer, we suggest disabling the cache here as well.
*/

'enabled' => false,

/**
* Define the length of time permissions should be cached for before being
* refreshed. Accepted values are either in seconds or as a DateInterval
* object. By default we cache for 86400 seconds (aka, 24 hours).
*/

'length' => 60 * 60 * 24,

/**
* When using a cache driver that supports tags, we'll tag the shinobi
* cache with this tag. This is useful for busting only the cache
* responsible for storing permissions and not anything else.
*/

'tag' => 'shinobi',

],

'models' => [

/*
Expand Down
22 changes: 16 additions & 6 deletions src/Concerns/HasPermissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,19 +119,19 @@ public function syncPermissions(...$permissions): self
* @param array $permissions
* @return Permission
*/
protected function getPermissions(array $permissions)
protected function getPermissions(array $collection)
{
return array_map(function($permission) {
$model = $this->getPermissionModel();

if ($permission instanceof $model) {
if ($permission instanceof Permission) {
return $permission->id;
}

$permission = $model->where('slug', $permission)->first();

return $permission->id;
}, $permissions);
}, $collection);
}

/**
Expand All @@ -144,7 +144,7 @@ protected function hasPermission($permission): bool
{
$model = $this->getPermissionModel();

if ($permission instanceof $model) {
if ($permission instanceof Permission) {
$permission = $permission->slug;
}

Expand All @@ -154,10 +154,20 @@ protected function hasPermission($permission): bool
/**
* Get the model instance responsible for permissions.
*
* @return \Caffeinated\Shinobi\Contracts\Permission
* @return \Caffeinated\Shinobi\Contracts\Permission|\Illuminate\Database\Eloquent\Collection
*/
protected function getPermissionModel(): Permission
protected function getPermissionModel()
{
if (config('shinobi.cache.enabled')) {
return cache()->tags(config('shinobi.cache.tag'))->remember(
'permissions',
config('shinobi.cache.length'),
function() {
return app()->make(config('shinobi.models.permission'))->get();
}
);
}

return app()->make(config('shinobi.models.permission'));
}
}
34 changes: 34 additions & 0 deletions src/Concerns/HasRoles.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,40 @@ public function hasRole($role): bool
return (bool) $this->roles->where('slug', $slug)->count();
}

/**
* Checks if the model has any of the given roles assigned.
*
* @param array $roles
* @return bool
*/
public function hasAnyRole(...$roles): bool
{
foreach ($roles as $role) {
if ($this->hasRole($role)) {
return true;
}
}

return false;
}

/**
* Checks if the model has all of the given roles assigned.
*
* @param array $roles
* @return bool
*/
public function hasAllRoles(...$roles): bool
{
foreach ($roles as $role) {
if (! $this->hasRole($role)) {
return false;
}
}

return true;
}

public function hasRoles(): bool
{
return (bool) $this->roles->count();
Expand Down
17 changes: 17 additions & 0 deletions src/Concerns/RefreshesPermissionCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Caffeinated\Shinobi\Concerns;

trait RefreshesPermissionCache
{
public static function bootRefreshesPermissionCache()
{
static::saved(function() {
cache()->tags(config('shinobi.cache.tag'))->flush();
});

static::deleted(function() {
cache()->tags(config('shinobi.cache.tag'))->flush();
});
}
}
3 changes: 3 additions & 0 deletions src/Models/Permission.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Caffeinated\Shinobi\Concerns\RefreshesPermissionCache;
use Caffeinated\Shinobi\Contracts\Permission as PermissionContract;

class Permission extends Model implements PermissionContract
{
use RefreshesPermissionCache;

/**
* The attributes that are fillable via mass assignment.
*
Expand Down
12 changes: 8 additions & 4 deletions src/ShinobiServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,9 @@

use Exception;
use Illuminate\Support\Facades\Gate;
use Caffeinated\Shinobi\Models\Role;
use Illuminate\Support\Facades\Blade;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
use Caffeinated\Shinobi\Facades\Shinobi;
use Caffeinated\Shinobi\Models\Permission;
use Illuminate\Contracts\Auth\Access\Authorizable;

class ShinobiServiceProvider extends ServiceProvider
Expand Down Expand Up @@ -76,6 +72,14 @@ protected function registerBladeDirectives()
Blade::if('role', function($role) {
return auth()->user() and auth()->user()->hasRole($role);
});

Blade::if('anyrole', function(...$roles) {
return auth()->user() and auth()->user()->hasAnyRole(...$roles);
});

Blade::if('allroles', function(...$roles) {
return auth()->user() and auth()->user()->hasAllRoles(...$roles);
});
}

/**
Expand Down
124 changes: 124 additions & 0 deletions tests/BladeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,128 @@ public function guests_do_not_have_roles()

$this->assertEquals($result, 'does not have admin or moderator roles');
}

/** @test */
public function the_anyrole_directive_evaluates_true_when_at_least_one_role_is_found()
{
$admin = factory(Role::class)->create([
'name' => 'Admin',
'slug' => 'admin',
]);

$user = factory(User::class)->create();

$user->assignRoles($admin);

$this->actingAs($user);

$result = $this->renderView('anyrole_directive');

$this->assertEquals($result, 'has either moderator or admin role');
}

/** @test */
public function the_anyrole_directive_evaluates_true_when_at_least_one_role_is_found_when_using_else()
{
$editor = factory(Role::class)->create([
'name' => 'Editor',
'slug' => 'editor',
]);

$user = factory(User::class)->create();

$user->assignRoles($editor);

$this->actingAs($user);

$result = $this->renderView('anyrole_directive');

$this->assertEquals($result, 'has either editor or contributor role');
}

/** @test */
public function the_anyrole_directive_evaluates_false_when_no_matching_role_is_found()
{
$vip = factory(Role::class)->create([
'name' => 'VIP',
'slug' => 'vip',
]);

$user = factory(User::class)->create();

$user->assignRoles($vip);

$this->actingAs($user);

$result = $this->renderView('anyrole_directive');

$this->assertEquals($result, 'does not have any of the defined roles');
}

/** @test */
public function the_allrole_directive_evaluates_true_when_all_roles_are_found()
{
$moderator = factory(Role::class)->create([
'name' => 'Moderator',
'slug' => 'moderator',
]);

$editor = factory(Role::class)->create([
'name' => 'Editor',
'slug' => 'editor',
]);

$user = factory(User::class)->create();

$user->assignRoles($moderator, $editor);

$this->actingAs($user);

$result = $this->renderView('allroles_directive');

$this->assertEquals($result, 'has both moderator and editor roles');
}

/** @test */
public function the_allrole_directive_evaluates_true_when_all_roles_are_found_when_using_else()
{
$vip = factory(Role::class)->create([
'name' => 'VIP',
'slug' => 'vip',
]);

$premium = factory(Role::class)->create([
'name' => 'Premium',
'slug' => 'premium',
]);

$user = factory(User::class)->create();

$user->assignRoles($vip, $premium);

$this->actingAs($user);

$result = $this->renderView('allroles_directive');

$this->assertEquals($result, 'has both VIP and premium roles');
}

/** @test */
public function the_allroles_directive_evaluates_false_when_all_defined_roles_are_not_met()
{
$vip = factory(Role::class)->create([
'name' => 'VIP',
'slug' => 'vip',
]);

$user = factory(User::class)->create();

$user->assignRoles($vip);

$this->actingAs($user);

$result = $this->renderView('allroles_directive');

$this->assertEquals($result, 'does not have any of the defined roles');
}
}
Loading

0 comments on commit d60e5b4

Please sign in to comment.