Skip to content

Commit a4c0acd

Browse files
committed
feat: add support for increments and iterables
1 parent 0932e85 commit a4c0acd

File tree

8 files changed

+330
-27
lines changed

8 files changed

+330
-27
lines changed

packages/cache/src/Cache.php

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Closure;
88
use Psr\Cache\CacheItemInterface;
9+
use Stringable;
910
use Tempest\DateTime\DateTimeInterface;
1011
use Tempest\DateTime\Duration;
1112

@@ -22,22 +23,54 @@ interface Cache
2223
/**
2324
* Sets the specified key to the specified value in the cache. Optionally, specify an expiration.
2425
*/
25-
public function put(string $key, mixed $value, null|Duration|DateTimeInterface $expiration = null): CacheItemInterface;
26+
public function put(Stringable|string $key, mixed $value, null|Duration|DateTimeInterface $expiration = null): CacheItemInterface;
27+
28+
/**
29+
* Sets the specified keys to the specified values in the cache. Optionally, specify an expiration.
30+
*
31+
* @template TKey of array-key
32+
* @template TValue
33+
*
34+
* @param iterable<TKey,TValue> $array
35+
* @return array<TKey,CacheItemInterface>
36+
*/
37+
public function putMany(iterable $values, null|Duration|DateTimeInterface $expiration = null): array;
2638

2739
/**
2840
* Gets the value associated with the specified key from the cache. If the key does not exist, null is returned.
2941
*/
30-
public function get(string $key): mixed;
42+
public function get(Stringable|string $key): mixed;
43+
44+
/**
45+
* Gets the values associated with the specified keys from the cache. If a key does not exist, null is returned for that key.
46+
*
47+
* @template TKey of array-key
48+
* @template TValue
49+
*
50+
* @param iterable<TKey,TValue> $array
51+
* @return array<TValue,mixed>
52+
*/
53+
public function getMany(iterable $key): array;
54+
55+
/**
56+
* Increments the value associated with the specified key by the specified amount. If the key does not exist, it is created with the specified amount.
57+
*/
58+
public function increment(Stringable|string $key, int $by = 1): int;
59+
60+
/**
61+
* Decrements the value associated with the specified key by the specified amount. If the key does not exist, it is created with the negative amount.
62+
*/
63+
public function decrement(Stringable|string $key, int $by = 1): int;
3164

3265
/**
3366
* If the specified key already exists in the cache, the value is returned and the `$callback` is not executed. Otherwise, the result of the callback is stored, then returned.
3467
*/
35-
public function resolve(string $key, Closure $callback, null|Duration|DateTimeInterface $expiration = null): mixed;
68+
public function resolve(Stringable|string $key, Closure $callback, null|Duration|DateTimeInterface $expiration = null): mixed;
3669

3770
/**
3871
* Removes the specified key from the cache.
3972
*/
40-
public function remove(string $key): void;
73+
public function remove(Stringable|string $key): void;
4174

4275
/**
4376
* Clears the entire cache.

packages/cache/src/GenericCache.php

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
use Closure;
66
use Psr\Cache\CacheItemInterface;
77
use Psr\Cache\CacheItemPoolInterface;
8+
use Stringable;
89
use Tempest\Cache\Config\CacheConfig;
910
use Tempest\DateTime\DateTime;
1011
use Tempest\DateTime\DateTimeInterface;
1112
use Tempest\DateTime\Duration;
13+
use Tempest\Support\Arr;
1214

1315
final class GenericCache implements Cache
1416
{
@@ -20,10 +22,10 @@ public function __construct(
2022
$this->adapter ??= $this->cacheConfig->createAdapter();
2123
}
2224

23-
public function put(string $key, mixed $value, null|Duration|DateTimeInterface $expiration = null): CacheItemInterface
25+
public function put(Stringable|string $key, mixed $value, null|Duration|DateTimeInterface $expiration = null): CacheItemInterface
2426
{
2527
$item = $this->adapter
26-
->getItem($key)
28+
->getItem((string) $key)
2729
->set($value);
2830

2931
if ($expiration instanceof Duration) {
@@ -41,37 +43,97 @@ public function put(string $key, mixed $value, null|Duration|DateTimeInterface $
4143
return $item;
4244
}
4345

44-
public function get(string $key): mixed
46+
public function putMany(iterable $values, null|Duration|DateTimeInterface $expiration = null): array
47+
{
48+
$items = [];
49+
50+
foreach ($values as $key => $value) {
51+
$items[(string) $key] = $this->put($key, $value, $expiration);
52+
}
53+
return $items;
54+
}
55+
56+
public function increment(Stringable|string $key, int $by = 1): int
57+
{
58+
if (! $this->enabled) {
59+
return 0;
60+
}
61+
62+
$item = $this->adapter->getItem((string) $key);
63+
64+
if (! $item->isHit()) {
65+
$item->set($by);
66+
} elseif (! is_numeric($item->get())) {
67+
throw new NotNumberException((string) $key);
68+
} else {
69+
$item->set(((int) $item->get()) + $by);
70+
}
71+
72+
$this->adapter->save($item);
73+
74+
return (int) $item->get();
75+
}
76+
77+
public function decrement(Stringable|string $key, int $by = 1): int
78+
{
79+
if (! $this->enabled) {
80+
return 0;
81+
}
82+
83+
$item = $this->adapter->getItem((string) $key);
84+
85+
if (! $item->isHit()) {
86+
$item->set(-$by);
87+
} elseif (! is_numeric($item->get())) {
88+
throw new NotNumberException((string) $key);
89+
} else {
90+
$item->set(((int) $item->get()) - $by);
91+
}
92+
93+
$this->adapter->save($item);
94+
95+
return (int) $item->get();
96+
}
97+
98+
public function get(Stringable|string $key): mixed
4599
{
46100
if (! $this->enabled) {
47101
return null;
48102
}
49103

50-
return $this->adapter->getItem($key)->get();
104+
return $this->adapter->getItem((string) $key)->get();
105+
}
106+
107+
public function getMany(iterable $key): array
108+
{
109+
return Arr\map_with_keys(
110+
array: $key,
111+
map: fn (string|Stringable $key) => yield (string) $key => $this->adapter->getItem((string) $key)->get(),
112+
);
51113
}
52114

53-
public function resolve(string $key, Closure $callback, null|Duration|DateTimeInterface $expiration = null): mixed
115+
public function resolve(Stringable|string $key, Closure $callback, null|Duration|DateTimeInterface $expiration = null): mixed
54116
{
55117
if (! $this->enabled) {
56118
return $callback();
57119
}
58120

59-
$item = $this->adapter->getItem($key);
121+
$item = $this->adapter->getItem((string) $key);
60122

61123
if (! $item->isHit()) {
62-
$item = $this->put($key, $callback(), $expiration);
124+
$item = $this->put((string) $key, $callback(), $expiration);
63125
}
64126

65127
return $item->get();
66128
}
67129

68-
public function remove(string $key): void
130+
public function remove(Stringable|string $key): void
69131
{
70132
if (! $this->enabled) {
71133
return;
72134
}
73135

74-
$this->adapter->deleteItem($key);
136+
$this->adapter->deleteItem((string) $key);
75137
}
76138

77139
public function clear(): void
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Tempest\Cache;
4+
5+
use Exception;
6+
7+
final class NotNumberException extends Exception implements CacheException
8+
{
9+
public function __construct(
10+
public readonly string $key,
11+
) {
12+
parent::__construct(
13+
message: "Cache key `{$key}` was not a number and could not be incremented or decremented.",
14+
);
15+
}
16+
}

packages/cache/src/Testing/RestrictedCache.php

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Closure;
66
use Psr\Cache\CacheItemInterface;
7+
use Stringable;
78
use Tempest\Cache\Cache;
89
use Tempest\Cache\ForbiddenCacheUsageException;
910
use Tempest\DateTime\DateTimeInterface;
@@ -16,22 +17,42 @@ public function __construct(
1617
private ?string $tag = null,
1718
) {}
1819

19-
public function put(string $key, mixed $value, null|Duration|DateTimeInterface $expiration = null): CacheItemInterface
20+
public function put(Stringable|string $key, mixed $value, null|Duration|DateTimeInterface $expiration = null): CacheItemInterface
2021
{
2122
throw new ForbiddenCacheUsageException($this->tag);
2223
}
2324

24-
public function get(string $key): mixed
25+
public function putMany(iterable $values, null|Duration|DateTimeInterface $expiration = null): array
2526
{
2627
throw new ForbiddenCacheUsageException($this->tag);
2728
}
2829

29-
public function resolve(string $key, Closure $callback, null|Duration|DateTimeInterface $expiration = null): mixed
30+
public function increment(Stringable|string $key, int $by = 1): int
3031
{
3132
throw new ForbiddenCacheUsageException($this->tag);
3233
}
3334

34-
public function remove(string $key): void
35+
public function decrement(Stringable|string $key, int $by = 1): int
36+
{
37+
throw new ForbiddenCacheUsageException($this->tag);
38+
}
39+
40+
public function get(Stringable|string $key): mixed
41+
{
42+
throw new ForbiddenCacheUsageException($this->tag);
43+
}
44+
45+
public function getMany(iterable $key): array
46+
{
47+
throw new ForbiddenCacheUsageException($this->tag);
48+
}
49+
50+
public function resolve(Stringable|string $key, Closure $callback, null|Duration|DateTimeInterface $expiration = null): mixed
51+
{
52+
throw new ForbiddenCacheUsageException($this->tag);
53+
}
54+
55+
public function remove(Stringable|string $key): void
3556
{
3657
throw new ForbiddenCacheUsageException($this->tag);
3758
}

packages/cache/src/Testing/TestingCache.php

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
use Closure;
66
use PHPUnit\Framework\Assert;
7+
use PHPUnit\Framework\AssertionFailedError;
78
use PHPUnit\Framework\ExpectationFailedException;
89
use Psr\Cache\CacheItemInterface;
910
use Psr\Clock\ClockInterface;
11+
use Stringable;
1012
use Symfony\Component\Cache\Adapter\ArrayAdapter;
1113
use Tempest\Cache\Cache;
1214
use Tempest\Cache\Config\CustomCacheConfig;
@@ -35,22 +37,42 @@ public function __construct(
3537
);
3638
}
3739

38-
public function put(string $key, mixed $value, null|Duration|DateTimeInterface $expiration = null): CacheItemInterface
40+
public function put(Stringable|string $key, mixed $value, null|Duration|DateTimeInterface $expiration = null): CacheItemInterface
3941
{
4042
return $this->cache->put($key, $value, $expiration);
4143
}
4244

43-
public function get(string $key): mixed
45+
public function putMany(iterable $values, null|Duration|DateTimeInterface $expiration = null): array
46+
{
47+
return $this->cache->putMany($values, $expiration);
48+
}
49+
50+
public function increment(Stringable|string $key, int $by = 1): int
51+
{
52+
return $this->cache->increment($key, $by);
53+
}
54+
55+
public function decrement(Stringable|string $key, int $by = 1): int
56+
{
57+
return $this->cache->decrement($key, $by);
58+
}
59+
60+
public function get(Stringable|string $key): mixed
4461
{
4562
return $this->cache->get($key);
4663
}
4764

48-
public function resolve(string $key, Closure $callback, null|Duration|DateTimeInterface $expiration = null): mixed
65+
public function getMany(iterable $key): array
66+
{
67+
return $this->cache->getMany($key);
68+
}
69+
70+
public function resolve(Stringable|string $key, Closure $callback, null|Duration|DateTimeInterface $expiration = null): mixed
4971
{
5072
return $this->cache->resolve($key, $callback, $expiration);
5173
}
5274

53-
public function remove(string $key): void
75+
public function remove(Stringable|string $key): void
5476
{
5577
$this->cache->remove($key);
5678
}
@@ -60,6 +82,38 @@ public function clear(): void
6082
$this->cache->clear();
6183
}
6284

85+
/**
86+
* Asserts that the given `$key` is cached and matches the expected `$value`.
87+
*/
88+
public function assertKeyHasValue(Stringable|string $key, mixed $value): self
89+
{
90+
$this->assertCached($key);
91+
92+
Assert::assertSame(
93+
expected: $value,
94+
actual: $this->get($key),
95+
message: "Cache key [{$key}] does not match the expected value.",
96+
);
97+
98+
return $this;
99+
}
100+
101+
/**
102+
* Asserts that the given `$key` is cached and does not match the given `$value`.
103+
*/
104+
public function assertKeyDoesNotHaveValue(Stringable|string $key, mixed $value): self
105+
{
106+
$this->assertCached($key);
107+
108+
Assert::assertNotSame(
109+
expected: $value,
110+
actual: $this->get($key),
111+
message: "Cache key [{$key}] matches the given value.",
112+
);
113+
114+
return $this;
115+
}
116+
63117
/**
64118
* Asserts that the given `$key` is cached.
65119
*

0 commit comments

Comments
 (0)