diff --git a/README.md b/README.md index 6d62f11..0a196ae 100644 --- a/README.md +++ b/README.md @@ -31,17 +31,17 @@ This package includes helpful global helpers for your project make almost anythi | | | | |---|---|---| -| [app_call](#app_call) | [in_console](#in_console) | [sleep_between](#sleep_between) -| [call_existing](#call_existing) | [in_development](#in_development) | [taptap](#taptap) -| [created](#created) | [logged_in](#logged_in) | [undot_path](#undot_path) -| [data_update](#data_update) | [methods_of](#methods_of) | [until](#until) -| [delist](#delist) | [missing_trait](#missing_trait) | [user](#user) -| [diff](#diff) | [none_of](#none_of) | [weekend](#weekend) -| [dot_path](#dot_path) | [ok](#ok) | [weekstart](#weekstart) -| [enclose](#enclose) | [period](#period) | [which_of](#which_of) -| [files](#files) | [period_from](#period_from) | [yesterday](#yesterday) -| [has_trait](#has_trait) | [pipe](#pipe) | -| [hashy](#hashy) | [route_is](#route_is) | +| [app_call](#app_call) | [in_console](#in_console) | [route_is](#route_is) +| [call_existing](#call_existing) | [in_development](#in_development) | [sleep_between](#sleep_between) +| [created](#created) | [logged_in](#logged_in) | [taptap](#taptap) +| [data_update](#data_update) | [methods_of](#methods_of) | [undot_path](#undot_path) +| [delist](#delist) | [missing_trait](#missing_trait) | [until](#until) +| [diff](#diff) | [none_of](#none_of) | [user](#user) +| [dot_path](#dot_path) | [ok](#ok) | [weekend](#weekend) +| [enclose](#enclose) | [period](#period) | [weekstart](#weekstart) +| [files](#files) | [period_from](#period_from) | [which_of](#which_of) +| [has_trait](#has_trait) | [pipe](#pipe) | [yesterday](#yesterday) +| [hashy](#hashy) | [remember](#remember) | ### `app_call()` @@ -401,6 +401,34 @@ pipe(10, [ // 15 ``` +### `remember()` + +Retrieves an item from the cache, or stores a default value if the item doesn't exist. + +```php +remember('foo', 60, function() { + return 'bar'; +}) +``` + +If no `ttl` is set, and rather a callback is issued as second parameter, it will be stored forever. + +```php +remember('foo', function () { + return 'bar'; +}) +``` + +It supports atomic locks, which are created using the same name key. It will lock the key by a given seconds, while also waiting for the same amount of time. + +```php +remember('foo', 60, function() { + return 'bar'; +}, 20); +``` + +This can be useful to avoid cache data-races, where multiple processes run the same callback because the cache key is not filled yet. + ### `route_is()` Determine whether the current route's name matches the given patterns. diff --git a/helpers/common.php b/helpers/common.php index 03bd24a..3eda884 100644 --- a/helpers/common.php +++ b/helpers/common.php @@ -135,6 +135,38 @@ function pipe(mixed $passable, array $pipes, callable $destination = null): mixe } } +if (!function_exists('remember')) { + /** + * Retrieves an item from the cache, or stores a default value if the item doesn't exist. + * + * @param string $key + * @param \Closure|\DateTimeInterface|\DateInterval|int $ttl + * @param \Closure|\DateTimeInterface|\DateInterval|int|null $callback + * @param int|null $lock If issued, it will lock the key and wait the same amount of seconds. + * + * @return mixed + */ + function remember( + string $key, + Closure|DateTimeInterface|DateInterval|int $ttl, + Closure|DateTimeInterface|DateInterval|int $callback = null, + int $lock = null + ): mixed + { + $cache = app('cache'); + + if (is_callable($ttl)) { + [$ttl, $callback, $lock] = [null, $ttl, $callback]; + } + + if ($lock) { + return $cache->lock($key, $lock)->block($ttl, static fn() => $cache->remember($key, $ttl, $callback)); + } + + return $cache->remember($key, $ttl, $callback); + } +} + if (!function_exists('sleep_between')) { /** * Runs a callback while sleeping between multiple executions. diff --git a/tests/CommonTest.php b/tests/CommonTest.php index a6b90f1..afe6cc7 100644 --- a/tests/CommonTest.php +++ b/tests/CommonTest.php @@ -3,6 +3,7 @@ namespace Tests; use Closure; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\HigherOrderTapProxy; use Illuminate\Support\Str; use Orchestra\Testbench\TestCase; @@ -33,6 +34,53 @@ public function test_data_update(): void static::assertNull($array['foo']['quz']['qux']); } + public function test_remember(): void + { + $result = remember('foo', static function():string { + return 'bar'; + }); + + static::assertSame('bar', $result); + + Cache::forget('foo'); + + $result = remember('foo', 10, static function(): string { + return 'bar'; + }); + + static::assertSame('bar', $result); + + Cache::put('foo', 'baz'); + + $result = remember('foo', 10, static function(): string { + return 'bar'; + }); + + static::assertSame('baz', $result); + + Cache::forget('foo'); + Cache::lock('foo', 2); + + $result = remember('foo', 10, static function (): string { + return 'bar'; + }, 10); + + static::assertSame('bar', $result); + + static::assertFalse(Cache::lock('foo', 2)->release()); + + Cache::forget('foo'); + Cache::lock('foo', 2); + + $result = remember('foo', static function (): string { + return 'bar'; + }, 10); + + static::assertSame('bar', $result); + + static::assertFalse(Cache::lock('foo', 2)->release()); + } + public function test_delist(): void { $list = ['foo' => 'bar', 'quz' => 'qux', 'quuz' => 'quux'];