Skip to content

Commit 8b7a6ec

Browse files
authored
Ability to cache single policy (#605)
* Ability to cache single policy * Add documentation * Wip * Resolve typo * fix typo * Test fix * wip
1 parent 1c29d53 commit 8b7a6ec

File tree

5 files changed

+86
-4
lines changed

5 files changed

+86
-4
lines changed

docs-v2/content/en/performance/performance.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ In order to improve performance, Restify caches the policies. You simply have to
2323

2424
The caching is tight to the current authenticated user so if another user is logged in, the cache will be hydrated for the new user once again.
2525

26+
Restify allows individual caching at the policy level with specific configurations. To enable this, a contract `Cacheable` must be implemented at the policy level, which enforces the use of the `cache()` method.
27+
28+
``` php
29+
class PostPolicy implements Cacheable
30+
{
31+
public function cache(): ?CarbonInterface
32+
{
33+
return now()->addMinutes();
34+
}
35+
```
36+
The `cache` method is expected to return a `CarbonInterface` or `null`. If `null` is returned, the current policy will `NOT` cached.
37+
2638
## Disable index meta
2739

2840
Index meta are policy information related to what actions are allowed on a resource for a specific user. However, if you don't need this information, you can disable the index meta by setting the `restify.repositories.serialize_index_meta` property to `false` in the `restify.php` configuration file:

src/Cache/Cacheable.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Cache;
4+
5+
use Carbon\CarbonInterface;
6+
7+
interface Cacheable
8+
{
9+
public function cache(): ?CarbonInterface;
10+
}

src/Cache/PolicyCache.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
namespace Binaryk\LaravelRestify\Cache;
44

55
use Closure;
6+
use Illuminate\Database\Eloquent\Model;
67
use Illuminate\Http\Request;
78
use Illuminate\Support\Facades\Cache;
9+
use Illuminate\Support\Facades\Gate;
810
use Illuminate\Support\Str;
911

1012
class PolicyCache
@@ -30,7 +32,7 @@ public static function keyForPolicyMethods(string $repositoryKey, string $policy
3032
return "restify.policy.$policyMethod.repository-$repositoryKey.resource-$modelKey.user-".$user?->getKey();
3133
}
3234

33-
public static function resolve(string $key, callable|Closure $data): mixed
35+
public static function resolve(string $key, callable|Closure $data, Model $model): mixed
3436
{
3537
if (! static::enabled()) {
3638
return $data();
@@ -40,7 +42,17 @@ public static function resolve(string $key, callable|Closure $data): mixed
4042
return Cache::get($key);
4143
}
4244

43-
Cache::put($key, $data = $data(), config('restify.cache.policies.ttl', 60));
45+
$policy = Gate::getPolicyFor($model);
46+
47+
$ttl = (method_exists($policy, 'cache') && $policy instanceof Cacheable)
48+
? $policy->cache()
49+
: config('restify.cache.policies.ttl', 60);
50+
51+
if (is_null($ttl)) {
52+
return $data;
53+
}
54+
55+
Cache::put($key, $data = $data(), $ttl);
4456

4557
return $data;
4658
}

src/Traits/AuthorizableModels.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public static function authorizedToUseRepository(Request $request): bool
3636
: false;
3737
};
3838

39-
return PolicyCache::resolve(PolicyCache::keyForAllowRestify(static::uriKey()), $resolver);
39+
return PolicyCache::resolve(PolicyCache::keyForAllowRestify(static::uriKey()), $resolver, static::newModel());
4040
}
4141

4242
/**
@@ -206,7 +206,8 @@ public function authorizedTo(Request $request, iterable|string $ability): bool
206206

207207
return PolicyCache::resolve(
208208
PolicyCache::keyForPolicyMethods(static::uriKey(), $ability, $this->resource->getKey()),
209-
fn () => Gate::check($ability, $this->resource)
209+
fn () => Gate::check($ability, $this->resource),
210+
$this->model(),
210211
);
211212
}
212213

tests/Feature/AuthorizableModelsTest.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@
22

33
namespace Binaryk\LaravelRestify\Tests\Feature;
44

5+
use Binaryk\LaravelRestify\Cache\Cacheable;
6+
use Binaryk\LaravelRestify\Cache\PolicyCache;
57
use Binaryk\LaravelRestify\Tests\Database\Factories\PostFactory;
68
use Binaryk\LaravelRestify\Tests\Fixtures\Post\Post;
79
use Binaryk\LaravelRestify\Tests\Fixtures\Post\PostPolicy;
810
use Binaryk\LaravelRestify\Tests\Fixtures\Post\PostRepository;
911
use Binaryk\LaravelRestify\Tests\Fixtures\User\User;
1012
use Binaryk\LaravelRestify\Tests\IntegrationTestCase;
13+
use Carbon\Carbon;
14+
use Carbon\CarbonInterface;
1115
use Illuminate\Foundation\Testing\RefreshDatabase;
1216
use Illuminate\Support\Facades\Cache;
1317
use Illuminate\Support\Facades\Gate;
18+
use Symfony\Component\ErrorHandler\ErrorHandler;
1419

1520
class AuthorizableModelsTest extends IntegrationTestCase
1621
{
@@ -121,4 +126,46 @@ public function test_can_disable_policies_cache(): void
121126
$this->getJson(PostRepository::route())
122127
->assertForbidden();
123128
}
129+
130+
public function test_can_enable_individual_policy_cache(): void
131+
{
132+
$this->freezeTime();
133+
134+
$this->partialMock(PostPolicy::class)
135+
->shouldReceive('show')
136+
->andReturn(true);
137+
138+
$this->partialMock(PostPolicy::class)
139+
->shouldReceive('cache')
140+
->once()
141+
->andReturn(now()->addMinutes(3));
142+
143+
Gate::policy(Post::class, PostPolicy::class);
144+
145+
/** @var Carbon $timeCached */
146+
$timeCached = Gate::getPolicyFor(Post::class)
147+
->cache();
148+
149+
$this->assertInstanceOf(CarbonInterface::class, $timeCached);
150+
151+
$post = PostFactory::one();
152+
153+
$this->getJson(PostRepository::route())
154+
->assertOk();
155+
156+
$this->assertTrue(Cache::has(PolicyCache::keyForPolicyMethods('posts', 'show', $post->getKey())));
157+
158+
$this->partialMock(PostPolicy::class)
159+
->shouldReceive('show')
160+
->andReturn(false);
161+
162+
$this->getJson(PostRepository::route())
163+
->assertOk();
164+
165+
$this->assertTrue(Cache::get(PolicyCache::keyForPolicyMethods('posts', 'show', $post->getKey())));
166+
167+
$this->travelTo(now()->addMinutes(5));
168+
169+
$this->assertFalse(Cache::has(PolicyCache::keyForPolicyMethods('posts', 'show', $post->getKey())));
170+
}
124171
}

0 commit comments

Comments
 (0)