Skip to content

Commit 67761b6

Browse files
committed
Simplify query cache helper usage
1 parent 4069477 commit 67761b6

2 files changed

Lines changed: 152 additions & 62 deletions

File tree

‎src/Database/Database.php‎

Lines changed: 101 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8633,30 +8633,25 @@ public function restoreQueryCacheDocuments(
86338633
* Execute a callback behind a cache-aside lookup.
86348634
*
86358635
* The callback runs on cache miss and its value is returned to the caller.
8636-
* The encode callback converts fresh values into cache payloads before save.
8637-
* The decode callback converts cached payloads back into return values; a
8638-
* literal false decode result rejects the cached payload and refreshes it.
8636+
* Query document payloads are converted to arrays before save and restored
8637+
* back into Documents on cache hits. A rejected document payload refreshes
8638+
* the cached value.
86398639
* A literal false value is treated as a cache miss and is not cacheable.
86408640
*
86418641
* @template T
8642-
* @template C
86438642
* @param string $key
86448643
* @param callable(): T $callback
86458644
* @param string $hash
8646-
* @param (callable(T): C)|null $encode
8647-
* @param (callable(C): (T|false))|null $decode
86488645
* @return T
8646+
* @throws AuthorizationException
8647+
* @throws Exception
86498648
*/
86508649
public function withCache(
86518650
string $key,
86528651
callable $callback,
86538652
string $hash = '',
8654-
?callable $encode = null,
8655-
?callable $decode = null,
86568653
): mixed {
86578654
$shouldRefreshCache = false;
8658-
$encode ??= static fn (mixed $value): mixed => $value;
8659-
$decode ??= static fn (mixed $value): mixed => $value;
86608655

86618656
try {
86628657
$cached = $this->cache->load($key, self::TTL, $hash);
@@ -8669,7 +8664,7 @@ public function withCache(
86698664
$value = \is_array($cached) && \array_key_exists('value', $cached) ? $cached['value'] : false;
86708665

86718666
if ($value !== false) {
8672-
$decoded = $decode($value);
8667+
$decoded = $this->decodeCacheValue($cached, $value);
86738668

86748669
if ($decoded !== false) {
86758670
return $decoded;
@@ -8691,7 +8686,7 @@ public function withCache(
86918686

86928687
if ($value !== false) {
86938688
try {
8694-
$this->cache->save($key, ['value' => $encode($value)], $hash);
8689+
$this->cache->save($key, $this->encodeCacheValue($value), $hash);
86958690
} catch (Throwable $e) {
86968691
Console::warning('Warning: Failed to save cache value: ' . $e->getMessage());
86978692
}
@@ -8700,6 +8695,100 @@ public function withCache(
87008695
return $value;
87018696
}
87028697

8698+
/**
8699+
* @param array<string, mixed> $cached
8700+
*/
8701+
private function decodeCacheValue(array $cached, mixed $value): mixed
8702+
{
8703+
$collection = $cached['collection'] ?? null;
8704+
if (!\is_string($collection) || $collection === '') {
8705+
return $value;
8706+
}
8707+
8708+
$collection = $this->silent(fn () => $this->getCollection($collection));
8709+
if ($collection->isEmpty()) {
8710+
return false;
8711+
}
8712+
8713+
if (($cached['type'] ?? null) === 'document') {
8714+
$documents = $this->restoreQueryCacheDocuments($collection, [$value]);
8715+
if ($documents === false) {
8716+
return false;
8717+
}
8718+
8719+
return $documents[0] ?? false;
8720+
}
8721+
8722+
return $this->restoreQueryCacheDocuments($collection, $value);
8723+
}
8724+
8725+
/**
8726+
* @return array<string, mixed>
8727+
*/
8728+
private function encodeCacheValue(mixed $value): array
8729+
{
8730+
if ($value instanceof Document) {
8731+
$collection = $value->getCollection();
8732+
if ($collection === '') {
8733+
return ['value' => $value];
8734+
}
8735+
8736+
return [
8737+
'collection' => $collection,
8738+
'type' => 'document',
8739+
'value' => $value->getArrayCopy(),
8740+
];
8741+
}
8742+
8743+
$collection = $this->getCacheValueCollection($value);
8744+
if ($collection === null || !\is_array($value)) {
8745+
return ['value' => $value];
8746+
}
8747+
8748+
return [
8749+
'collection' => $collection,
8750+
'type' => 'documents',
8751+
'value' => $this->encodeQueryCacheValue($value),
8752+
];
8753+
}
8754+
8755+
private function getCacheValueCollection(mixed $value): ?string
8756+
{
8757+
if ($value instanceof Document) {
8758+
return $value->getCollection() ?: null;
8759+
}
8760+
8761+
if (!\is_array($value)) {
8762+
return null;
8763+
}
8764+
8765+
foreach ($value as $item) {
8766+
$collection = $this->getCacheValueCollection($item);
8767+
if ($collection !== null) {
8768+
return $collection;
8769+
}
8770+
}
8771+
8772+
return null;
8773+
}
8774+
8775+
private function encodeQueryCacheValue(mixed $value): mixed
8776+
{
8777+
if ($value instanceof Document) {
8778+
return $value->getArrayCopy();
8779+
}
8780+
8781+
if (!\is_array($value)) {
8782+
return $value;
8783+
}
8784+
8785+
foreach ($value as $key => $item) {
8786+
$value[$key] = $this->encodeQueryCacheValue($item);
8787+
}
8788+
8789+
return $value;
8790+
}
8791+
87038792
/**
87048793
* Helper method to iterate documents in collection using callback pattern
87058794
* Alterative is

‎tests/unit/QueryCacheTest.php‎

Lines changed: 51 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,6 @@ private function findWithCache(
5454
key: $cacheKey,
5555
callback: fn (): array => $database->find($collection, $queries, $forPermission),
5656
hash: $cacheHash,
57-
encode: static fn (array $documents): array => \array_map(
58-
static fn (Document $document): array => $document->getArrayCopy(),
59-
$documents,
60-
),
61-
decode: fn (mixed $payload): array|false => $database->restoreQueryCacheDocuments(
62-
collection: $collectionDocument,
63-
payload: $payload,
64-
forPermission: $forPermission,
65-
),
6657
);
6758
}
6859

@@ -227,77 +218,79 @@ function () use (&$callbackCalls): string {
227218
$this->assertSame(2, $callbackCalls);
228219
}
229220

230-
public function testWithCacheEncodesSavedValueAndDecodesCachedValue(): void
221+
public function testWithCacheCachesSingleDocument(): void
231222
{
232223
$cache = new HashMemoryCache();
233224
$database = $this->createDatabase($cache);
225+
$database->createCollection('wafRules', permissions: [
226+
Permission::read(Role::any()),
227+
Permission::create(Role::any()),
228+
]);
229+
230+
$database->createDocument('wafRules', new Document([
231+
'$id' => 'rule-a',
232+
]));
234233

235234
$callbackCalls = 0;
235+
$collection = $database->getCollection('wafRules');
236+
$key = $database->getQueryCacheKey($collection->getId(), '_39');
237+
$hash = $database->getQueryCacheField($collection, field: 'document');
236238

237239
$first = $database->withCache(
238-
key: 'key',
239-
callback: function () use (&$callbackCalls): array {
240+
key: $key,
241+
callback: function () use ($database, &$callbackCalls): Document {
240242
$callbackCalls++;
241-
return ['fresh'];
243+
return $database->getDocument('wafRules', 'rule-a');
242244
},
243-
encode: static fn (array $value): array => ['encoded' => $value],
244-
decode: static fn (array $value): array => $value['encoded'],
245+
hash: $hash,
245246
);
246-
247247
$second = $database->withCache(
248-
key: 'key',
249-
callback: function () use (&$callbackCalls): array {
248+
key: $key,
249+
callback: function () use ($database, &$callbackCalls): Document {
250250
$callbackCalls++;
251-
return ['miss'];
251+
return $database->getDocument('wafRules', 'missing');
252252
},
253-
encode: static fn (array $value): array => ['encoded' => $value],
254-
decode: static fn (array $value): array => $value['encoded'],
253+
hash: $hash,
255254
);
256255

257-
$this->assertSame(['fresh'], $first);
258-
$this->assertSame(['fresh'], $second);
256+
$this->assertSame('rule-a', $first->getId());
257+
$this->assertSame('rule-a', $second->getId());
259258
$this->assertSame(1, $callbackCalls);
260259
}
261260

262-
public function testWithCacheRefreshesWhenDecodeRejectsCachedValue(): void
261+
public function testWithCacheCachesStaticQueryValues(): void
263262
{
264263
$cache = new HashMemoryCache();
265264
$database = $this->createDatabase($cache);
265+
$database->createCollection('wafRules', permissions: [
266+
Permission::read(Role::any()),
267+
]);
266268

267269
$callbackCalls = 0;
270+
$collection = $database->getCollection('wafRules');
271+
$key = $database->getQueryCacheKey($collection->getId(), '_39');
272+
$hash = $database->getQueryCacheField($collection, field: 'count');
268273

269-
$database->withCache(
270-
key: 'key',
271-
callback: function () use (&$callbackCalls): array {
272-
$callbackCalls++;
273-
return ['stale'];
274-
},
275-
encode: static fn (array $value): array => $value,
276-
);
277-
278-
$fresh = $database->withCache(
279-
key: 'key',
280-
callback: function () use (&$callbackCalls): array {
274+
$first = $database->withCache(
275+
key: $key,
276+
callback: function () use (&$callbackCalls): int {
281277
$callbackCalls++;
282-
return ['fresh'];
278+
return 10;
283279
},
284-
encode: static fn (array $value): array => $value,
285-
decode: static fn (array $value): array|false => $value === ['stale'] ? false : $value,
280+
hash: $hash,
286281
);
287-
288-
$cachedFresh = $database->withCache(
289-
key: 'key',
290-
callback: function () use (&$callbackCalls): array {
282+
$second = $database->withCache(
283+
key: $key,
284+
callback: function () use (&$callbackCalls): int {
291285
$callbackCalls++;
292-
return ['miss'];
286+
return 20;
293287
},
294-
encode: static fn (array $value): array => $value,
295-
decode: static fn (array $value): array|false => $value === ['stale'] ? false : $value,
288+
hash: $hash,
296289
);
297290

298-
$this->assertSame(['fresh'], $fresh);
299-
$this->assertSame(['fresh'], $cachedFresh);
300-
$this->assertSame(2, $callbackCalls);
291+
$this->assertSame(10, $first);
292+
$this->assertSame(10, $second);
293+
$this->assertSame(1, $callbackCalls);
301294
}
302295

303296
public function testQueryCacheUsesCacheUntilPurged(): void
@@ -594,6 +587,8 @@ public function testQueryCacheRehydratesNestedDocumentPayloads(): void
594587
$cache->save(
595588
$database->getQueryCacheKey($collection->getId(), '_39'),
596589
[
590+
'collection' => $collection->getId(),
591+
'type' => 'documents',
597592
'value' => [
598593
[
599594
'$id' => 'parent-a',
@@ -648,7 +643,11 @@ public function testQueryCacheRefreshesInvalidPayload(): void
648643
$collection = $database->getCollection('wafRules');
649644
$cache->save(
650645
$database->getQueryCacheKey($collection->getId(), '_39'),
651-
['value' => 'invalid'],
646+
[
647+
'collection' => $collection->getId(),
648+
'type' => 'documents',
649+
'value' => 'invalid',
650+
],
652651
$database->getQueryCacheField($collection, $queries),
653652
);
654653

@@ -696,6 +695,8 @@ public function testQueryCacheRefreshesInvalidPayloadEntry(): void
696695
$cache->save(
697696
$database->getQueryCacheKey($collection->getId(), '_39'),
698697
[
698+
'collection' => $collection->getId(),
699+
'type' => 'documents',
699700
'value' => [
700701
[
701702
'$id' => 'rule-a',

0 commit comments

Comments
 (0)