Skip to content
8 changes: 4 additions & 4 deletions system/Cache/CacheInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ public function save(string $key, mixed $value, int $ttl = 60): bool;
* Attempts to get an item from the cache, or executes the callback
* and stores the result on cache miss.
*
* @param string $key Cache item name
* @param int $ttl Time To Live, in seconds
* @param Closure(): mixed $callback Callback executed on cache miss
* @param string $key Cache item name
* @param callable(): int|callable(mixed): int|int $ttl Time To Live, in seconds
* @param Closure(): mixed $callback Callback executed on cache miss
*/
public function remember(string $key, int $ttl, Closure $callback): mixed;
public function remember(string $key, callable|int $ttl, Closure $callback): mixed;

/**
* Deletes a specific item from the cache store.
Expand Down
6 changes: 5 additions & 1 deletion system/Cache/Handlers/ApcuHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,14 @@ public function save(string $key, $value, int $ttl = 60): bool
return apcu_store($key, $value, $ttl);
}

public function remember(string $key, int $ttl, Closure $callback): mixed
public function remember(string $key, callable|int $ttl, Closure $callback): mixed
{
$key = static::validateKey($key, $this->prefix);

if (is_callable($ttl)) {
return parent::remember($key, $ttl, $callback);
}

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

Expand Down
26 changes: 24 additions & 2 deletions system/Cache/Handlers/BaseHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use CodeIgniter\Cache\CacheInterface;
use CodeIgniter\Exceptions\InvalidArgumentException;
use Config\Cache;
use ReflectionFunction;

/**
* Base class for cache handling
Expand Down Expand Up @@ -64,15 +65,36 @@ public static function validateKey($key, $prefix = ''): string
return strlen($prefix . $key) > static::MAX_KEY_LENGTH ? $prefix . md5($key) : $prefix . $key;
}

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

if ($value !== null) {
return $value;
}

$this->save($key, $value = $callback(), $ttl);
$value = $callback();

if (is_callable($ttl)) {
$ttlClosure = Closure::fromCallable($ttl);
$rf = new ReflectionFunction($ttlClosure);
$params = $rf->getNumberOfRequiredParameters();

if ($params === 0) {
/** @var Closure(): int $ttlClosure */
$ttl = $ttlClosure();
} elseif ($params === 1) {
/** @var Closure(mixed): int $ttlClosure */
$ttl = $ttlClosure($value);
} else {
throw new InvalidArgumentException(sprintf(
'Argument #2 ($ttl) must accept 0 or 1 parameter, %d given.',
$params,
));
}
}

$this->save($key, $value, $ttl);

return $value;
}
Expand Down
2 changes: 1 addition & 1 deletion system/Cache/Handlers/DummyHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function get(string $key): mixed
return null;
}

public function remember(string $key, int $ttl, Closure $callback): mixed
public function remember(string $key, callable|int $ttl, Closure $callback): mixed
{
return null;
}
Expand Down
27 changes: 25 additions & 2 deletions system/Test/Mock/MockCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
use CodeIgniter\Cache\Handlers\BaseHandler;
use CodeIgniter\Cache\LockStoreInterface;
use CodeIgniter\Cache\LockStoreProviderInterface;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\I18n\Time;
use PHPUnit\Framework\Assert;
use ReflectionFunction;

class MockCache extends BaseHandler implements CacheInterface, LockStoreProviderInterface
{
Expand Down Expand Up @@ -72,15 +74,36 @@ public function get(string $key): mixed
*
* @return bool|null
*/
public function remember(string $key, int $ttl, Closure $callback): mixed
public function remember(string $key, callable|int $ttl, Closure $callback): mixed
{
$value = $this->get($key);

if ($value !== null) {
return $value;
}

$this->save($key, $value = $callback(), $ttl);
$value = $callback();

if (is_callable($ttl)) {
$ttlClosure = Closure::fromCallable($ttl);
$rf = new ReflectionFunction($ttlClosure);
$params = $rf->getNumberOfRequiredParameters();

if ($params === 0) {
/** @var Closure(): int $ttlClosure */
$ttl = $ttlClosure();
} elseif ($params === 1) {
/** @var Closure(mixed): int $ttlClosure */
$ttl = $ttlClosure($value);
} else {
throw new InvalidArgumentException(sprintf(
'Argument #2 ($ttl) must accept 0 or 1 parameter, %d given.',
$params,
));
}
}

$this->save($key, $value, $ttl);

return $value;
}
Expand Down
44 changes: 44 additions & 0 deletions tests/system/Cache/Handlers/ApcuHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use CodeIgniter\Cache\CacheFactory;
use CodeIgniter\CLI\CLI;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\I18n\Time;
use Config\Cache;
use PHPUnit\Framework\Attributes\Group;
Expand Down Expand Up @@ -92,6 +93,49 @@ public function testRemember(): void
$this->assertNull($this->handler->get(self::$key1));
}

/**
* This test waits for 3 seconds before last assertion so this
* is naturally a "slow" test on the perspective of the default limit.
*
* @timeLimit 3.5
*/
public function testRememberWithTTLCallable(): void
{
$this->handler->remember(self::$key1, static fn (): int => 2, static fn (): string => 'value');

$this->assertSame('value', $this->handler->get(self::$key1));
$this->assertNull($this->handler->get(self::$dummy));

CLI::wait(3);
$this->assertNull($this->handler->get(self::$key1));
}

/**
* This test waits for 3 seconds before last assertion so this
* is naturally a "slow" test on the perspective of the default limit.
*
* @timeLimit 3.5
*/
public function testRememberWithTTLCallableAndValuePassed(): void
{
$this->handler->remember(self::$key1, static fn ($value): int => $value[0], static fn (): array => [2, 3]);

$this->assertSame([2, 3], $this->handler->get(self::$key1));
$this->assertNull($this->handler->get(self::$dummy));

CLI::wait(3);
$this->assertNull($this->handler->get(self::$key1));
}

public function testRememberWithTTLCallableAndMultipleParameters(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Argument #2 ($ttl) must accept 0 or 1 parameter, 2 given.');

/** @phpstan-ignore argument.type */
$this->handler->remember(self::$key1, static fn ($a, $b): int => 2, static fn (): string => 'value');
}

public function testSave(): void
{
$this->assertTrue($this->handler->save(self::$key1, 'value'));
Expand Down
14 changes: 14 additions & 0 deletions tests/system/Cache/Handlers/DummyHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ public function testRemember(): void
$this->assertNull($dummyHandler);
}

public function testRememberWithTTLCallable(): void
{
$dummyHandler = $this->handler->remember('key', static fn (): int => 2, static fn (): string => 'value');

$this->assertNull($dummyHandler);
}

public function testRememberWithTTLCallableAndValuePassed(): void
{
$dummyHandler = $this->handler->remember('key', static fn ($value): int => $value[0], static fn (): array => [2, 3]);

$this->assertNull($dummyHandler);
}

public function testSave(): void
{
$this->assertTrue($this->handler->save('key', 'value'));
Expand Down
44 changes: 44 additions & 0 deletions tests/system/Cache/Handlers/FileHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use CodeIgniter\Cache\CacheFactory;
use CodeIgniter\Cache\Exceptions\CacheException;
use CodeIgniter\CLI\CLI;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\I18n\Time;
use Config\Cache;
use PHPUnit\Framework\Attributes\DataProvider;
Expand Down Expand Up @@ -144,6 +145,49 @@ public function testRemember(): void
$this->assertNull($this->handler->get(self::$key1));
}

/**
* This test waits for 3 seconds before last assertion so this
* is naturally a "slow" test on the perspective of the default limit.
*
* @timeLimit 3.5
*/
public function testRememberWithTTLCallable(): void
{
$this->handler->remember(self::$key1, static fn (): int => 2, static fn (): string => 'value');

$this->assertSame('value', $this->handler->get(self::$key1));
$this->assertNull($this->handler->get(self::$dummy));

CLI::wait(3);
$this->assertNull($this->handler->get(self::$key1));
}

/**
* This test waits for 3 seconds before last assertion so this
* is naturally a "slow" test on the perspective of the default limit.
*
* @timeLimit 3.5
*/
public function testRememberWithTTLCallableAndValuePassed(): void
{
$this->handler->remember(self::$key1, static fn ($value): int => $value[0], static fn (): array => [2, 3]);

$this->assertSame([2, 3], $this->handler->get(self::$key1));
$this->assertNull($this->handler->get(self::$dummy));

CLI::wait(3);
$this->assertNull($this->handler->get(self::$key1));
}

public function testRememberWithTTLCallableAndMultipleParameters(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Argument #2 ($ttl) must accept 0 or 1 parameter, 2 given.');

/** @phpstan-ignore argument.type */
$this->handler->remember(self::$key1, static fn ($a, $b): int => 2, static fn (): string => 'value');
}

/**
* chmod('path', 0444) does not work on Windows
*/
Expand Down
44 changes: 44 additions & 0 deletions tests/system/Cache/Handlers/MemcachedHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use CodeIgniter\Cache\LockStoreProviderInterface;
use CodeIgniter\CLI\CLI;
use CodeIgniter\Exceptions\BadMethodCallException;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\I18n\Time;
use Config\Cache;
use PHPUnit\Framework\Attributes\Group;
Expand Down Expand Up @@ -101,6 +102,49 @@ public function testRemember(): void
$this->assertNull($this->handler->get(self::$key1));
}

/**
* This test waits for 3 seconds before last assertion so this
* is naturally a "slow" test on the perspective of the default limit.
*
* @timeLimit 3.5
*/
public function testRememberWithTTLCallable(): void
{
$this->handler->remember(self::$key1, static fn (): int => 2, static fn (): string => 'value');

$this->assertSame('value', $this->handler->get(self::$key1));
$this->assertNull($this->handler->get(self::$dummy));

CLI::wait(3);
$this->assertNull($this->handler->get(self::$key1));
}

/**
* This test waits for 3 seconds before last assertion so this
* is naturally a "slow" test on the perspective of the default limit.
*
* @timeLimit 3.5
*/
public function testRememberWithTTLCallableAndValuePassed(): void
{
$this->handler->remember(self::$key1, static fn ($value): int => $value[0], static fn (): array => [2, 3]);

$this->assertSame([2, 3], $this->handler->get(self::$key1));
$this->assertNull($this->handler->get(self::$dummy));

CLI::wait(3);
$this->assertNull($this->handler->get(self::$key1));
}

public function testRememberWithTTLCallableAndMultipleParameters(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Argument #2 ($ttl) must accept 0 or 1 parameter, 2 given.');

/** @phpstan-ignore argument.type */
$this->handler->remember(self::$key1, static fn ($a, $b): int => 2, static fn (): string => 'value');
}

public function testSave(): void
{
$this->assertTrue($this->handler->save(self::$key1, 'value'));
Expand Down
44 changes: 44 additions & 0 deletions tests/system/Cache/Handlers/PredisHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use CodeIgniter\Cache\LockStoreInterface;
use CodeIgniter\Cache\LockStoreProviderInterface;
use CodeIgniter\CLI\CLI;
use CodeIgniter\Exceptions\InvalidArgumentException;
use CodeIgniter\I18n\Time;
use Config\Cache;
use PHPUnit\Framework\Attributes\Group;
Expand Down Expand Up @@ -109,6 +110,49 @@ public function testRemember(): void
$this->assertNull($this->handler->get(self::$key1));
}

/**
* This test waits for 3 seconds before last assertion so this
* is naturally a "slow" test on the perspective of the default limit.
*
* @timeLimit 3.5
*/
public function testRememberWithTTLCallable(): void
{
$this->handler->remember(self::$key1, static fn (): int => 2, static fn (): string => 'value');

$this->assertSame('value', $this->handler->get(self::$key1));
$this->assertNull($this->handler->get(self::$dummy));

CLI::wait(3);
$this->assertNull($this->handler->get(self::$key1));
}

/**
* This test waits for 3 seconds before last assertion so this
* is naturally a "slow" test on the perspective of the default limit.
*
* @timeLimit 3.5
*/
public function testRememberWithTTLCallableAndValuePassed(): void
{
$this->handler->remember(self::$key1, static fn ($value): int => $value[0], static fn (): array => [2, 3]);

$this->assertSame([2, 3], $this->handler->get(self::$key1));
$this->assertNull($this->handler->get(self::$dummy));

CLI::wait(3);
$this->assertNull($this->handler->get(self::$key1));
}

public function testRememberWithTTLCallableAndMultipleParameters(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Argument #2 ($ttl) must accept 0 or 1 parameter, 2 given.');

/** @phpstan-ignore argument.type */
$this->handler->remember(self::$key1, static fn ($a, $b): int => 2, static fn (): string => 'value');
}

public function testSave(): void
{
$this->assertTrue($this->handler->save(self::$key1, 'value'));
Expand Down
Loading
Loading