Skip to content

Commit 6b560bc

Browse files
Merge pull request #36 from Laragear/feat/flexible-caching
[4.x] Adds Flexible Caching
2 parents 49495f6 + 00ce2ed commit 6b560bc

File tree

6 files changed

+71
-17
lines changed

6 files changed

+71
-17
lines changed

.stubs/stubs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ namespace Illuminate\Database\Query {
1010
/**
1111
* Caches the underlying query results.
1212
*
13-
* @param \DateTimeInterface|\DateInterval|int|bool|null $ttl
13+
* @param \DateTimeInterface|\DateInterval|int|bool|array{ 0: \DateTimeInterface|\DateInterval|int, 1: \DateTimeInterface|\DateInterval|int }|null $ttl
1414
* @param string $key
1515
* @param string|null $store
1616
* @param int $wait
17-
* @return static
17+
* @return $this
1818
*/
1919
public function cache(
20-
DateTimeInterface|DateInterval|int|bool|null $ttl = null,
20+
DateTimeInterface|DateInterval|int|bool|array|null $ttl = null,
2121
string $key = '',
2222
string $store = null,
2323
int $wait = 0,
@@ -37,14 +37,14 @@ namespace Illuminate\Database\Eloquent {
3737
/**
3838
* Caches the underlying query results.
3939
*
40-
* @param \DateTimeInterface|\DateInterval|int|bool|null $ttl
40+
* @param \DateTimeInterface|\DateInterval|int|bool|array{ 0: \DateTimeInterface|\DateInterval|int, 1: \DateTimeInterface|\DateInterval|int }|null $ttl
4141
* @param string $key
4242
* @param string|null $store
4343
* @param int $wait
44-
* @return static
44+
* @return $this
4545
*/
4646
public function cache(
47-
DateTimeInterface|DateInterval|int|bool|null $ttl = null,
47+
DateTimeInterface|DateInterval|int|bool|array|null $ttl = null,
4848
string $key = '',
4949
string $store = null,
5050
int $wait = 0,

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ Article::latest('published_at')->take(10)->cache(store: 'redis')->get();
109109

110110
### Cache Lock (data races)
111111

112-
On multiple processes, the query may be executed multiple times until the first process is able to store the result in the cache, specially when these take more than one second. Take, for example, 1,000 users reading the latest 10 post of a site at the same time will call the database 100 times.
112+
On multiple processes, the query may be executed multiple times until the first process is able to store the result in the cache, specially when these take more than one second. Take, for example, 1,000 users reading the latest 10 post of a site at the same time will call the database 1,000 times.
113113

114114
To avoid this, set the `wait` parameter with the number of seconds to hold the acquired lock.
115115

@@ -123,6 +123,16 @@ The first process will acquire the lock for the given seconds and execute the qu
123123

124124
> If you need a more advanced locking mechanism, use the [cache lock](https://laravel.com/docs/cache#managing-locks-across-processes) directly.
125125
126+
### Stale while revalidate
127+
128+
You may take advantage of [Laravel Flexible Caching mechanism](https://laravel.com/docs/11.x/cache#swr) by issuing an array of values as first argument. (...) _The first value in the array represents the number of seconds the cache is considered fresh, while the second value defines how long it can be served as stale data before recalculation is necessary_.
129+
130+
```php
131+
use App\Models\Article;
132+
133+
Article::latest('published_at')->take(200)->cache([5, 300])->get();
134+
```
135+
126136
## Forgetting results with a key
127137

128138
Cache keys are used to identify multiple queries cached with an identifiable name. These are not mandatory, but if you expect to remove a query from the cache, you will need to identify the query with the `key` argument.

src/CacheAwareConnectionProxy.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717
use function base64_encode;
1818
use function cache;
1919
use function config;
20+
use function count;
2021
use function implode;
22+
use function is_array;
2123
use function is_int;
2224
use function max;
2325
use function md5;
26+
use function method_exists;
2427
use function rtrim;
2528

2629
class CacheAwareConnectionProxy extends Connection
@@ -40,7 +43,7 @@ class CacheAwareConnectionProxy extends Connection
4043
public function __construct(
4144
public ConnectionInterface $connection,
4245
protected Repository $repository,
43-
protected DateTimeInterface|DateInterval|int|null $ttl,
46+
protected DateTimeInterface|DateInterval|int|array|null $ttl,
4447
protected int $lockWait,
4548
protected string $cachePrefix,
4649
public string $userKey = '',
@@ -81,7 +84,11 @@ public function select($query, $bindings = [], $useReadPdo = true)
8184
if ($results === null) {
8285
$results = $this->connection->select($query, $bindings, $useReadPdo);
8386

84-
$this->repository->put($key, $results, $this->ttl);
87+
if (is_array($this->ttl) && count($this->ttl) > 1 && method_exists($this->repository, 'flexible')) {
88+
$this->repository->flexible($key, $results, $this->ttl);
89+
} else {
90+
$this->repository->put($key, $results, $this->ttl);
91+
}
8592

8693
// If the user added a user key, we will append this computed key to it and save it.
8794
if ($this->userKey) {
@@ -181,7 +188,7 @@ protected function getTimestamp(DateInterval|DateTimeInterface|int $expiration):
181188
return now()->add($expiration)->getTimestamp();
182189
}
183190

184-
return now()->addRealSeconds($expiration)->getTimestamp();
191+
return now()->addSeconds($expiration)->getTimestamp();
185192
}
186193

187194
/**
@@ -217,7 +224,7 @@ public function __call($method, $arguments)
217224
*/
218225
public static function crateNewInstance(
219226
ConnectionInterface $connection,
220-
DateTimeInterface|DateInterval|int|null $ttl,
227+
DateTimeInterface|DateInterval|int|array|null $ttl,
221228
string $key,
222229
int $wait,
223230
?string $store,

src/CacheQuery.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ protected function getQueries(array $keys): Collection
7575
$keys = $this->addPrefix($keys);
7676

7777
// Merging the keys last allows us to ensure the parent keys are deleted last.
78-
// @phpstan-ignore-next-line
7978
return Collection::make($this->repository()->getMultiple($keys))->filter()->flatMap->list->merge($keys);
8079
}
8180
}

src/CacheQueryServiceProvider.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,15 @@ public function boot(): void
6060
protected function macro(): Closure
6161
{
6262
return function (
63-
DateTimeInterface|DateInterval|int|bool|null $ttl = 60,
63+
DateTimeInterface|DateInterval|int|bool|array|null $ttl = 60,
6464
string $key = '',
65-
string $store = null,
65+
?string $store = null,
6666
int $wait = 0,
6767
): Builder {
6868
/** @var \Illuminate\Database\Query\Builder $this */
6969

7070
// Avoid re-wrapping the connection into another proxy.
71-
if ($this->connection instanceof CacheAwareConnectionProxy) {
71+
if ($this->connection instanceof CacheAwareConnectionProxy) { // @phpstan-ignore-line
7272
$this->connection = $this->connection->connection;
7373
}
7474

@@ -88,9 +88,9 @@ protected function macro(): Closure
8888
protected function eloquentMacro(): Closure
8989
{
9090
return function (
91-
DateTimeInterface|DateInterval|int|bool|null $ttl = 60,
91+
DateTimeInterface|DateInterval|int|bool|array|null $ttl = 60,
9292
string $key = '',
93-
string $store = null,
93+
?string $store = null,
9494
int $wait = 0,
9595
): EloquentBuilder {
9696
/**

tests/CacheAwareConnectionProxyTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Tests;
44

55
use Closure;
6+
use Illuminate\Cache\Repository as CacheRepository;
67
use Illuminate\Contracts\Cache\Lock;
78
use Illuminate\Contracts\Cache\LockProvider;
89
use Illuminate\Contracts\Cache\Repository;
@@ -20,6 +21,7 @@
2021

2122
use function floor;
2223
use function max;
24+
use function method_exists;
2325
use function now;
2426
use function today;
2527

@@ -43,6 +45,10 @@ protected function setUp(): void
4345
'user_id' => (int) floor(max(1, $i / 2)),
4446
])->toArray());
4547

48+
if (method_exists($this, 'withoutDefer')) {
49+
$this->withoutDefer();
50+
}
51+
4652
CacheAwareConnectionProxy::$queryHasher = null;
4753
});
4854

@@ -439,6 +445,38 @@ public function test_regenerates_cache_using_ttl_with_negative_number(): void
439445
static::assertSame('test', $result->name);
440446
}
441447

448+
public function test_uses_flexible_caching_when_using_ttl_as_array_of_values(): void
449+
{
450+
if (! method_exists(CacheRepository::class, 'flexible')) {
451+
$this->markTestSkipped('Cannot test flexible caching if repository does not implements it.');
452+
}
453+
454+
$hash = 'cache-query|fj8Xyz4K1Zh0tdAamPbG1A';
455+
456+
$repository = $this->mock(CacheRepository::class);
457+
$repository->expects('put')->never();
458+
$repository->expects('flexible')->with($hash, Mockery::type('array'), [5, 300])->once();
459+
$repository->expects('getMultiple')->with([$hash, ''])->times(1)->andReturn(['' => null, $hash => null]);
460+
461+
$this->mock('cache')->shouldReceive('store')->with(null)->andReturn($repository);
462+
463+
$this->app->make('db')->table('users')->where('id', 1)->cache([5, 300])->first();
464+
}
465+
466+
public function test_doesnt_uses_flexible_caching_if_repository_is_not_flexible(): void
467+
{
468+
$hash = 'cache-query|fj8Xyz4K1Zh0tdAamPbG1A';
469+
470+
$repository = $this->mock(Repository::class);
471+
$repository->expects('flexible')->never();
472+
$repository->expects('put')->with($hash, Mockery::type('array'), [5, 300])->once();
473+
$repository->expects('getMultiple')->with([$hash, ''])->times(1)->andReturn(['' => null, $hash => null]);
474+
475+
$this->mock('cache')->shouldReceive('store')->with(null)->andReturn($repository);
476+
477+
$this->app->make('db')->table('users')->where('id', 1)->cache([5, 300])->first();
478+
}
479+
442480
public function test_different_queries_with_same_key_add_to_same_list(): void
443481
{
444482
$this->app->make('db')->table('users')->cache(null, 'foo')->where('id', 1)->first();

0 commit comments

Comments
 (0)