Skip to content

Commit 12fba3f

Browse files
feat: Add support for callable TTLs in cache handlers (#10159)
* feat: Added support for callable for TTL in Cache Handlers * remove redundant reference in changelog * fix Psalm error * Add explicit types and throw exception if number of required args > 1 * rebase * Added tests to check expection thrown * cs-fix * Added phpstan-ignore to explicitly check for exception * fix rebase problems * Fix double prefixing in APCu Handler * Make the changelog more explicit regarding custom implementation Co-authored-by: Michal Sniatala <michal@sniatala.pl> * fix the backticks Co-authored-by: Michal Sniatala <michal@sniatala.pl> * Apply code suggestion * wrap the callable in parentheses * apply suggestion --------- Co-authored-by: Michal Sniatala <michal@sniatala.pl>
1 parent 1a3987f commit 12fba3f

14 files changed

Lines changed: 294 additions & 13 deletions

File tree

system/Cache/CacheInterface.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ public function save(string $key, mixed $value, int $ttl = 60): bool;
4444
* Attempts to get an item from the cache, or executes the callback
4545
* and stores the result on cache miss.
4646
*
47-
* @param string $key Cache item name
48-
* @param int $ttl Time To Live, in seconds
49-
* @param Closure(): mixed $callback Callback executed on cache miss
47+
* @param string $key Cache item name
48+
* @param (callable(mixed): int)|int $ttl Time To Live, in seconds
49+
* @param Closure(): mixed $callback Callback executed on cache miss
5050
*/
51-
public function remember(string $key, int $ttl, Closure $callback): mixed;
51+
public function remember(string $key, callable|int $ttl, Closure $callback): mixed;
5252

5353
/**
5454
* Deletes a specific item from the cache store.

system/Cache/Handlers/ApcuHandler.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,12 @@ public function save(string $key, $value, int $ttl = 60): bool
5555
return apcu_store($key, $value, $ttl);
5656
}
5757

58-
public function remember(string $key, int $ttl, Closure $callback): mixed
58+
public function remember(string $key, callable|int $ttl, Closure $callback): mixed
5959
{
60+
if (is_callable($ttl)) {
61+
return parent::remember($key, $ttl, $callback);
62+
}
63+
6064
$key = static::validateKey($key, $this->prefix);
6165

6266
return apcu_entry($key, $callback, $ttl);

system/Cache/Handlers/BaseHandler.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,21 @@ public static function validateKey($key, $prefix = ''): string
6464
return strlen($prefix . $key) > static::MAX_KEY_LENGTH ? $prefix . md5($key) : $prefix . $key;
6565
}
6666

67-
public function remember(string $key, int $ttl, Closure $callback): mixed
67+
public function remember(string $key, callable|int $ttl, Closure $callback): mixed
6868
{
6969
$value = $this->get($key);
7070

7171
if ($value !== null) {
7272
return $value;
7373
}
7474

75-
$this->save($key, $value = $callback(), $ttl);
75+
$value = $callback();
76+
77+
if (is_callable($ttl)) {
78+
$ttl = $ttl($value);
79+
}
80+
81+
$this->save($key, $value, $ttl);
7682

7783
return $value;
7884
}

system/Cache/Handlers/DummyHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function get(string $key): mixed
3131
return null;
3232
}
3333

34-
public function remember(string $key, int $ttl, Closure $callback): mixed
34+
public function remember(string $key, callable|int $ttl, Closure $callback): mixed
3535
{
3636
return null;
3737
}

system/Test/Mock/MockCache.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,21 @@ public function get(string $key): mixed
7272
*
7373
* @return bool|null
7474
*/
75-
public function remember(string $key, int $ttl, Closure $callback): mixed
75+
public function remember(string $key, callable|int $ttl, Closure $callback): mixed
7676
{
7777
$value = $this->get($key);
7878

7979
if ($value !== null) {
8080
return $value;
8181
}
8282

83-
$this->save($key, $value = $callback(), $ttl);
83+
$value = $callback();
84+
85+
if (is_callable($ttl)) {
86+
$ttl = $ttl($value);
87+
}
88+
89+
$this->save($key, $value, $ttl);
8490

8591
return $value;
8692
}

tests/system/Cache/Handlers/ApcuHandlerTest.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace CodeIgniter\Cache\Handlers;
1515

16+
use ArgumentCountError;
1617
use CodeIgniter\Cache\CacheFactory;
1718
use CodeIgniter\CLI\CLI;
1819
use CodeIgniter\I18n\Time;
@@ -92,6 +93,48 @@ public function testRemember(): void
9293
$this->assertNull($this->handler->get(self::$key1));
9394
}
9495

96+
/**
97+
* This test waits for 3 seconds before last assertion so this
98+
* is naturally a "slow" test on the perspective of the default limit.
99+
*
100+
* @timeLimit 3.5
101+
*/
102+
public function testRememberWithTTLCallable(): void
103+
{
104+
$this->handler->remember(self::$key1, static fn (): int => 2, static fn (): string => 'value');
105+
106+
$this->assertSame('value', $this->handler->get(self::$key1));
107+
$this->assertNull($this->handler->get(self::$dummy));
108+
109+
CLI::wait(3);
110+
$this->assertNull($this->handler->get(self::$key1));
111+
}
112+
113+
/**
114+
* This test waits for 3 seconds before last assertion so this
115+
* is naturally a "slow" test on the perspective of the default limit.
116+
*
117+
* @timeLimit 3.5
118+
*/
119+
public function testRememberWithTTLCallableAndValuePassed(): void
120+
{
121+
$this->handler->remember(self::$key1, static fn ($value): int => $value[0], static fn (): array => [2, 3]);
122+
123+
$this->assertSame([2, 3], $this->handler->get(self::$key1));
124+
$this->assertNull($this->handler->get(self::$dummy));
125+
126+
CLI::wait(3);
127+
$this->assertNull($this->handler->get(self::$key1));
128+
}
129+
130+
public function testRememberWithTTLCallableAndMultipleParameters(): void
131+
{
132+
$this->expectException(ArgumentCountError::class);
133+
134+
/** @phpstan-ignore argument.type */
135+
$this->handler->remember(self::$key1, static fn ($a, $b): int => 2, static fn (): string => 'value');
136+
}
137+
95138
public function testSave(): void
96139
{
97140
$this->assertTrue($this->handler->save(self::$key1, 'value'));

tests/system/Cache/Handlers/DummyHandlerTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,20 @@ public function testRemember(): void
4848
$this->assertNull($dummyHandler);
4949
}
5050

51+
public function testRememberWithTTLCallable(): void
52+
{
53+
$dummyHandler = $this->handler->remember('key', static fn (): int => 2, static fn (): string => 'value');
54+
55+
$this->assertNull($dummyHandler);
56+
}
57+
58+
public function testRememberWithTTLCallableAndValuePassed(): void
59+
{
60+
$dummyHandler = $this->handler->remember('key', static fn ($value): int => $value[0], static fn (): array => [2, 3]);
61+
62+
$this->assertNull($dummyHandler);
63+
}
64+
5165
public function testSave(): void
5266
{
5367
$this->assertTrue($this->handler->save('key', 'value'));

tests/system/Cache/Handlers/FileHandlerTest.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace CodeIgniter\Cache\Handlers;
1515

16+
use ArgumentCountError;
1617
use CodeIgniter\Cache\CacheFactory;
1718
use CodeIgniter\Cache\Exceptions\CacheException;
1819
use CodeIgniter\CLI\CLI;
@@ -144,6 +145,48 @@ public function testRemember(): void
144145
$this->assertNull($this->handler->get(self::$key1));
145146
}
146147

148+
/**
149+
* This test waits for 3 seconds before last assertion so this
150+
* is naturally a "slow" test on the perspective of the default limit.
151+
*
152+
* @timeLimit 3.5
153+
*/
154+
public function testRememberWithTTLCallable(): void
155+
{
156+
$this->handler->remember(self::$key1, static fn (): int => 2, static fn (): string => 'value');
157+
158+
$this->assertSame('value', $this->handler->get(self::$key1));
159+
$this->assertNull($this->handler->get(self::$dummy));
160+
161+
CLI::wait(3);
162+
$this->assertNull($this->handler->get(self::$key1));
163+
}
164+
165+
/**
166+
* This test waits for 3 seconds before last assertion so this
167+
* is naturally a "slow" test on the perspective of the default limit.
168+
*
169+
* @timeLimit 3.5
170+
*/
171+
public function testRememberWithTTLCallableAndValuePassed(): void
172+
{
173+
$this->handler->remember(self::$key1, static fn ($value): int => $value[0], static fn (): array => [2, 3]);
174+
175+
$this->assertSame([2, 3], $this->handler->get(self::$key1));
176+
$this->assertNull($this->handler->get(self::$dummy));
177+
178+
CLI::wait(3);
179+
$this->assertNull($this->handler->get(self::$key1));
180+
}
181+
182+
public function testRememberWithTTLCallableAndMultipleParameters(): void
183+
{
184+
$this->expectException(ArgumentCountError::class);
185+
186+
/** @phpstan-ignore argument.type */
187+
$this->handler->remember(self::$key1, static fn ($a, $b): int => 2, static fn (): string => 'value');
188+
}
189+
147190
/**
148191
* chmod('path', 0444) does not work on Windows
149192
*/

tests/system/Cache/Handlers/MemcachedHandlerTest.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace CodeIgniter\Cache\Handlers;
1515

16+
use ArgumentCountError;
1617
use CodeIgniter\Cache\CacheFactory;
1718
use CodeIgniter\Cache\LockStoreInterface;
1819
use CodeIgniter\Cache\LockStoreProviderInterface;
@@ -101,6 +102,48 @@ public function testRemember(): void
101102
$this->assertNull($this->handler->get(self::$key1));
102103
}
103104

105+
/**
106+
* This test waits for 3 seconds before last assertion so this
107+
* is naturally a "slow" test on the perspective of the default limit.
108+
*
109+
* @timeLimit 3.5
110+
*/
111+
public function testRememberWithTTLCallable(): void
112+
{
113+
$this->handler->remember(self::$key1, static fn (): int => 2, static fn (): string => 'value');
114+
115+
$this->assertSame('value', $this->handler->get(self::$key1));
116+
$this->assertNull($this->handler->get(self::$dummy));
117+
118+
CLI::wait(3);
119+
$this->assertNull($this->handler->get(self::$key1));
120+
}
121+
122+
/**
123+
* This test waits for 3 seconds before last assertion so this
124+
* is naturally a "slow" test on the perspective of the default limit.
125+
*
126+
* @timeLimit 3.5
127+
*/
128+
public function testRememberWithTTLCallableAndValuePassed(): void
129+
{
130+
$this->handler->remember(self::$key1, static fn ($value): int => $value[0], static fn (): array => [2, 3]);
131+
132+
$this->assertSame([2, 3], $this->handler->get(self::$key1));
133+
$this->assertNull($this->handler->get(self::$dummy));
134+
135+
CLI::wait(3);
136+
$this->assertNull($this->handler->get(self::$key1));
137+
}
138+
139+
public function testRememberWithTTLCallableAndMultipleParameters(): void
140+
{
141+
$this->expectException(ArgumentCountError::class);
142+
143+
/** @phpstan-ignore argument.type */
144+
$this->handler->remember(self::$key1, static fn ($a, $b): int => 2, static fn (): string => 'value');
145+
}
146+
104147
public function testSave(): void
105148
{
106149
$this->assertTrue($this->handler->save(self::$key1, 'value'));

tests/system/Cache/Handlers/PredisHandlerTest.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace CodeIgniter\Cache\Handlers;
1515

16+
use ArgumentCountError;
1617
use CodeIgniter\Cache\CacheFactory;
1718
use CodeIgniter\Cache\LockStoreInterface;
1819
use CodeIgniter\Cache\LockStoreProviderInterface;
@@ -109,6 +110,48 @@ public function testRemember(): void
109110
$this->assertNull($this->handler->get(self::$key1));
110111
}
111112

113+
/**
114+
* This test waits for 3 seconds before last assertion so this
115+
* is naturally a "slow" test on the perspective of the default limit.
116+
*
117+
* @timeLimit 3.5
118+
*/
119+
public function testRememberWithTTLCallable(): void
120+
{
121+
$this->handler->remember(self::$key1, static fn (): int => 2, static fn (): string => 'value');
122+
123+
$this->assertSame('value', $this->handler->get(self::$key1));
124+
$this->assertNull($this->handler->get(self::$dummy));
125+
126+
CLI::wait(3);
127+
$this->assertNull($this->handler->get(self::$key1));
128+
}
129+
130+
/**
131+
* This test waits for 3 seconds before last assertion so this
132+
* is naturally a "slow" test on the perspective of the default limit.
133+
*
134+
* @timeLimit 3.5
135+
*/
136+
public function testRememberWithTTLCallableAndValuePassed(): void
137+
{
138+
$this->handler->remember(self::$key1, static fn ($value): int => $value[0], static fn (): array => [2, 3]);
139+
140+
$this->assertSame([2, 3], $this->handler->get(self::$key1));
141+
$this->assertNull($this->handler->get(self::$dummy));
142+
143+
CLI::wait(3);
144+
$this->assertNull($this->handler->get(self::$key1));
145+
}
146+
147+
public function testRememberWithTTLCallableAndMultipleParameters(): void
148+
{
149+
$this->expectException(ArgumentCountError::class);
150+
151+
/** @phpstan-ignore argument.type */
152+
$this->handler->remember(self::$key1, static fn ($a, $b): int => 2, static fn (): string => 'value');
153+
}
154+
112155
public function testSave(): void
113156
{
114157
$this->assertTrue($this->handler->save(self::$key1, 'value'));

0 commit comments

Comments
 (0)