Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Database/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -8611,7 +8611,7 @@ public function purgeCachedQueries(string $collection, ?string $namespace = null
* @throws AuthorizationException
* @throws Exception
*/
public function withCache(
private function withCache(
string $key,
callable $callback,
?string $hash = '',
Expand Down
86 changes: 63 additions & 23 deletions tests/unit/QueryCacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ private function createDatabase(Adapter $cache, array $filters = [], ?DatabaseMe
return $database;
}

/**
* @template T
* @param callable(): T $callback
* @return T
*/
private function withCache(
Database $database,
string $key,
callable $callback,
?string $hash = '',
): mixed {
$method = new \ReflectionMethod(Database::class, 'withCache');

/** @var T */
return $method->invoke($database, $key, $callback, $hash);
}

Comment on lines +37 to +48

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Reflection-based test helper is fragile

new \ReflectionMethod(Database::class, 'withCache') will throw a ReflectionException at runtime if the method is renamed or removed, with no compile-time signal. The same helper is duplicated in WithCacheLeaseTest, so a rename would silently break two test files. Consider naming the helper more explicitly (e.g. invokeWithCache) or centralising it in a shared trait so the single 'withCache' string literal is easy to find and update.

Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/unit/QueryCacheTest.php
Line: 37-48

Comment:
**Reflection-based test helper is fragile**

`new \ReflectionMethod(Database::class, 'withCache')` will throw a `ReflectionException` at runtime if the method is renamed or removed, with no compile-time signal. The same helper is duplicated in `WithCacheLeaseTest`, so a rename would silently break two test files. Consider naming the helper more explicitly (e.g. `invokeWithCache`) or centralising it in a shared trait so the single `'withCache'` string literal is easy to find and update.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Codex

/**
* @param array<Query> $queries
* @return array<Document>
Expand All @@ -49,7 +66,8 @@ private function findWithCache(
$cacheKey = $database->getQueryCacheKey($collectionDocument->getId(), $namespace);
$cacheHash = $database->getQueryCacheField($collectionDocument, $queries);

return $database->withCache(
return $this->withCache(
$database,
key: $cacheKey,
callback: fn (): array => $database->find($collection, $queries),
hash: $cacheHash,
Expand All @@ -63,7 +81,8 @@ public function testWithCacheUsesCallbackOnMissAndCachesResult(): void

$callbackCalls = 0;

$value = $database->withCache(
$value = $this->withCache(
$database,
'key',
function () use (&$callbackCalls): array {
$callbackCalls++;
Expand All @@ -74,7 +93,8 @@ function () use (&$callbackCalls): array {
$this->assertSame(['value' => 'fresh'], $value);
$this->assertSame(1, $callbackCalls);

$value = $database->withCache(
$value = $this->withCache(
$database,
'key',
function () use (&$callbackCalls): array {
$callbackCalls++;
Expand All @@ -93,7 +113,8 @@ public function testWithCacheCachesEmptyValues(): void

$callbackCalls = 0;

$value = $database->withCache(
$value = $this->withCache(
$database,
'key',
function () use (&$callbackCalls): array {
$callbackCalls++;
Expand All @@ -103,7 +124,8 @@ function () use (&$callbackCalls): array {

$this->assertSame([], $value);

$value = $database->withCache(
$value = $this->withCache(
$database,
'key',
function () use (&$callbackCalls): array {
$callbackCalls++;
Expand All @@ -122,7 +144,8 @@ public function testWithCacheCachesNullValues(): void

$callbackCalls = 0;

$value = $database->withCache(
$value = $this->withCache(
$database,
'key',
function () use (&$callbackCalls): mixed {
$callbackCalls++;
Expand All @@ -132,7 +155,8 @@ function () use (&$callbackCalls): mixed {

$this->assertNull($value);

$value = $database->withCache(
$value = $this->withCache(
$database,
'key',
function () use (&$callbackCalls): string {
$callbackCalls++;
Expand All @@ -152,7 +176,8 @@ public function testWithCacheSeparatesPayloadsByHashField(): void
$firstCalls = 0;
$secondCalls = 0;

$first = $database->withCache(
$first = $this->withCache(
$database,
'key',
function () use (&$firstCalls): array {
$firstCalls++;
Expand All @@ -161,7 +186,8 @@ function () use (&$firstCalls): array {
'first-field',
);

$second = $database->withCache(
$second = $this->withCache(
$database,
'key',
function () use (&$secondCalls): array {
$secondCalls++;
Expand All @@ -170,7 +196,8 @@ function () use (&$secondCalls): array {
'second-field',
);

$cachedFirst = $database->withCache(
$cachedFirst = $this->withCache(
$database,
'key',
function () use (&$firstCalls): array {
$firstCalls++;
Expand All @@ -194,7 +221,8 @@ public function testWithCacheDoesNotCacheFalseValues(): void

$callbackCalls = 0;

$value = $database->withCache(
$value = $this->withCache(
$database,
'key',
function () use (&$callbackCalls): bool {
$callbackCalls++;
Expand All @@ -205,7 +233,8 @@ function () use (&$callbackCalls): bool {
$this->assertFalse($value);
$this->assertSame([], $cache->list('key'));

$value = $database->withCache(
$value = $this->withCache(
$database,
'key',
function () use (&$callbackCalls): string {
$callbackCalls++;
Expand All @@ -224,15 +253,17 @@ public function testWithCacheBypassesCacheForNullHash(): void

$callbackCalls = 0;

$first = $database->withCache(
$first = $this->withCache(
$database,
key: 'key',
callback: function () use (&$callbackCalls): string {
$callbackCalls++;
return 'first';
},
hash: null,
);
$second = $database->withCache(
$second = $this->withCache(
$database,
key: 'key',
callback: function () use (&$callbackCalls): string {
$callbackCalls++;
Expand All @@ -254,14 +285,16 @@ public function testWithCacheDoesNotCacheCollectionlessDocuments(): void

$callbackCalls = 0;

$first = $database->withCache(
$first = $this->withCache(
$database,
key: 'key',
callback: function () use (&$callbackCalls): Document {
$callbackCalls++;
return new Document(['$id' => 'first']);
},
);
$second = $database->withCache(
$second = $this->withCache(
$database,
key: 'key',
callback: function () use (&$callbackCalls): Document {
$callbackCalls++;
Expand Down Expand Up @@ -290,14 +323,16 @@ public function testWithCacheDoesNotCacheMixedDocumentArrays(): void

$callbackCalls = 0;

$first = $database->withCache(
$first = $this->withCache(
$database,
key: 'key',
callback: function () use ($database, &$callbackCalls): array {
$callbackCalls++;
return ['prefix', $database->getDocument('wafRules', 'rule-a')];
},
);
$second = $database->withCache(
$second = $this->withCache(
$database,
key: 'key',
callback: function () use ($database, &$callbackCalls): array {
$callbackCalls++;
Expand Down Expand Up @@ -329,15 +364,17 @@ public function testWithCacheCachesSingleDocument(): void
$key = $database->getQueryCacheKey($collection->getId(), '_39');
$hash = $database->getQueryCacheField($collection, field: 'document');

$first = $database->withCache(
$first = $this->withCache(
$database,
key: $key,
callback: function () use ($database, &$callbackCalls): Document {
$callbackCalls++;
return $database->getDocument('wafRules', 'rule-a');
},
hash: $hash,
);
$second = $database->withCache(
$second = $this->withCache(
$database,
key: $key,
callback: function () use ($database, &$callbackCalls): Document {
$callbackCalls++;
Expand All @@ -364,15 +401,17 @@ public function testWithCacheCachesStaticQueryValues(): void
$key = $database->getQueryCacheKey($collection->getId(), '_39');
$hash = $database->getQueryCacheField($collection, field: 'count');

$first = $database->withCache(
$first = $this->withCache(
$database,
key: $key,
callback: function () use (&$callbackCalls): int {
$callbackCalls++;
return 10;
},
hash: $hash,
);
$second = $database->withCache(
$second = $this->withCache(
$database,
key: $key,
callback: function () use (&$callbackCalls): int {
$callbackCalls++;
Expand Down Expand Up @@ -694,7 +733,8 @@ public function testQueryCacheFiltersDocumentSecurityPayloadsOnHit(): void
], $hash);

$callbackCalls = 0;
$documents = $database->withCache(
$documents = $this->withCache(
$database,
key: $key,
callback: function () use (&$callbackCalls): array {
$callbackCalls++;
Expand Down
20 changes: 18 additions & 2 deletions tests/unit/WithCacheLeaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ protected function setUp(): void
$this->key = $this->database->getQueryCacheKey('projects');
}

/**
* @template T
* @param callable(): T $callback
* @return T
*/
private function withCache(
string $key,
callable $callback,
?string $hash = '',
): mixed {
$method = new \ReflectionMethod(Database::class, 'withCache');

/** @var T */
return $method->invoke($this->database, $key, $callback, $hash);
}

public function testStaleListWriteAfterConcurrentPurgeIsRejected(): void
{
$hash = 'list-hash';
Expand All @@ -52,7 +68,7 @@ public function testStaleListWriteAfterConcurrentPurgeIsRejected(): void
// concurrent writer purges the query key after the read started but
// before the result is cached. Without a lease the stale list below
// would land in the cache after the purge.
$result = $this->database->withCache($this->key, function () use ($hash, $document) {
$result = $this->withCache($this->key, function () use ($hash, $document) {
$this->cacheAdapter->purge($this->key, $hash);

return [$document];
Expand All @@ -71,7 +87,7 @@ public function testListWriteLandsWhenNoConcurrentPurge(): void

$document = $this->database->getDocument('projects', 'project');

$result = $this->database->withCache($this->key, fn () => [$document], $hash);
$result = $this->withCache($this->key, fn () => [$document], $hash);

$this->assertCount(1, $result);
$this->assertNotFalse(
Expand Down
Loading