Skip to content

Commit 1cf3aa3

Browse files
committed
refine cache invalidation and runtime controls
1 parent a7e472f commit 1cf3aa3

35 files changed

Lines changed: 951 additions & 431 deletions

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,13 @@ Writes through cache-aware Eloquent or Query Builder paths invalidate automatica
7474
Use the facade after writes performed elsewhere:
7575

7676
```php
77+
use App\Models\Comment;
78+
use App\Models\Post;
7779
use NormCache\Facades\NormCache;
7880

79-
NormCache::invalidateTable('mysql', 'posts');
80-
NormCache::invalidateTables('mysql', ['posts', 'comments']);
81+
NormCache::invalidate('posts', connection: 'mysql');
82+
NormCache::invalidate([Post::class, Comment::class]);
83+
NormCache::invalidate(['posts', 'comments'], connection: 'mysql');
8184
NormCache::flushTag('homepage');
8285
NormCache::flushAll();
8386
```
@@ -141,7 +144,7 @@ NormCache bypasses reads when correctness cannot be established, including:
141144

142145
Canonical storage requires a supported single-column integer or string primary key. Queries can still use `result` storage when canonical routing is unavailable.
143146

144-
Writes performed through raw SQL or a connection not installed by NormCache are invisible until `invalidateTable()`, `invalidateTables()`, or `flushAll()` is called. After changing connection database/schema metadata at runtime, call `NormCache::clearSchemaMetadata()` for that connection.
147+
Writes performed through raw SQL or a connection not installed by NormCache are invisible until `invalidate()` or `flushAll()` is called. After changing connection database/schema metadata at runtime, call `NormCache::clearSchemaMetadata()` for that connection.
145148

146149
## Redis Cluster
147150

src/Cache/CacheStateResolver.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
{
1313
public function __construct(
1414
private RuntimeState $runtime,
15+
private CacheSwitch $switch,
1516
private RedisStore $store,
1617
private CacheKeyBuilder $keys,
1718
) {}
@@ -227,6 +228,6 @@ public function fetch(array $keys, array $alsoFetch = []): array
227228

228229
private function epoch(): string
229230
{
230-
return $this->runtime->epoch(fn(): string => $this->store->getRaw($this->keys->epoch()) ?? '0');
231+
return $this->switch->epoch();
231232
}
232233
}

src/Cache/CacheSwitch.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace NormCache\Cache;
4+
5+
use NormCache\Support\CacheKeyBuilder;
6+
use NormCache\Support\RedisStore;
7+
use NormCache\Values\CacheConfig;
8+
use NormCache\Values\RuntimeState;
9+
use Throwable;
10+
11+
final readonly class CacheSwitch
12+
{
13+
public function __construct(
14+
private CacheConfig $config,
15+
private RuntimeState $runtime,
16+
private RedisStore $store,
17+
private CacheKeyBuilder $keys,
18+
) {}
19+
20+
public function readable(): bool
21+
{
22+
if (!$this->config->enabled) {
23+
return false;
24+
}
25+
26+
if (!$this->runtime->available()) {
27+
return true;
28+
}
29+
30+
try {
31+
return !$this->runtime->state(fn(): array => $this->readPair())[1];
32+
} catch (Throwable $exception) {
33+
$this->runtime->fail($exception);
34+
35+
return false;
36+
}
37+
}
38+
39+
public function invalidating(): bool
40+
{
41+
if (!$this->config->enabled) {
42+
return false;
43+
}
44+
45+
if (!$this->runtime->available()) {
46+
return true;
47+
}
48+
49+
try {
50+
return !$this->runtime->runtimeDisabled(fn(): bool => $this->readFlag());
51+
} catch (Throwable) {
52+
return true;
53+
}
54+
}
55+
56+
public function epoch(): string
57+
{
58+
return $this->runtime->epoch(fn(): array => $this->readPair());
59+
}
60+
61+
/** @return array{0: string, 1: bool} */
62+
private function readPair(): array
63+
{
64+
$epochKey = $this->keys->epoch();
65+
$disabledKey = $this->keys->disabled();
66+
$values = $this->store->mget([$epochKey, $disabledKey]);
67+
68+
return [
69+
$values[$epochKey] ?? '0',
70+
($values[$disabledKey] ?? null) !== null,
71+
];
72+
}
73+
74+
private function readFlag(): bool
75+
{
76+
return $this->store->getRaw($this->keys->disabled()) !== null;
77+
}
78+
}

src/Cache/CanonicalRepository.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace NormCache\Cache;
44

55
use Closure;
6-
use NormCache\Database\CachingQueryBuilder;
6+
use NormCache\Database\QueryBuilder;
77
use NormCache\Enums\CacheReadOutcome;
88
use NormCache\Payload\MembershipCodec;
99
use NormCache\Payload\RawResultCodec;
@@ -163,7 +163,7 @@ public function read(
163163

164164
/** @param array<int, mixed> $rows */
165165
public function publish(
166-
CachingQueryBuilder $query,
166+
QueryBuilder $query,
167167
QueryPlan $plan,
168168
CacheState $state,
169169
array $rows,
@@ -205,7 +205,7 @@ public function publish(
205205
expectedVersion: $state->version,
206206
expectedGeneration: $state->generation,
207207
membershipPayload: $membership,
208-
membershipTtl: $query->normCacheTtl() ?? $this->config->queryTtl,
208+
membershipTtl: $query->configuredTtl() ?? $this->config->queryTtl,
209209
rowTtl: $this->config->rowTtl,
210210
buildingKey: $lease->buildingKey,
211211
wakeKey: (string) $lease->wakeKey,

src/Cache/Engine.php

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

55
use Illuminate\Database\Connection;
66
use InvalidArgumentException;
7-
use NormCache\Database\CachingQueryBuilder;
7+
use NormCache\Database\QueryBuilder;
88
use NormCache\Enums\CacheReadOutcome;
99
use NormCache\Payload\RawResultCodec;
1010
use NormCache\Planning\DependencyAnalyzer;
@@ -38,6 +38,7 @@ public function __construct(
3838
private RawResultCodec $codec,
3939
private DependencyAnalyzer $dependencies,
4040
private Reporter $reporter,
41+
private CacheSwitch $switch,
4142
private CacheStateResolver $states,
4243
private CanonicalRepository $canonical,
4344
private ResultRepository $results,
@@ -48,14 +49,14 @@ public function __construct(
4849
* @param callable(): array $primaryDatabase
4950
*/
5051
public function select(
51-
CachingQueryBuilder $query,
52+
QueryBuilder $query,
5253
string $sql,
5354
array $bindings,
5455
string $operation,
5556
callable $database,
5657
callable $primaryDatabase,
5758
): array {
58-
if (!$this->config->enabled || !$this->runtime->available()) {
59+
if (!$this->switch->readable()) {
5960
return $database();
6061
}
6162

@@ -74,7 +75,7 @@ public function select(
7475
return $database();
7576
}
7677

77-
if ($table->isView && $query->normCacheDependencies() === []) {
78+
if ($table->isView && $query->dependencies() === []) {
7879
$this->reporter->bypass(
7980
$query,
8081
'view_dependencies_required',
@@ -119,7 +120,7 @@ public function select(
119120
$analysis->opaque && $directRoot === null,
120121
$operation,
121122
);
122-
$namespace = $this->identity->namespace($query->normCacheTag());
123+
$namespace = $this->identity->namespace($query->configuredTag());
123124
$canonicalQueryHash = null;
124125

125126
try {
@@ -280,7 +281,7 @@ private function readOutcome(array $result): CacheReadOutcome
280281

281282
/** @param array{hit: bool, rows: array, reason: ?string, outcome?: CacheReadOutcome} $result */
282283
private function reportRead(
283-
CachingQueryBuilder $query,
284+
QueryBuilder $query,
284285
QueryPlan $plan,
285286
string $queryHash,
286287
string $sql,
@@ -300,7 +301,7 @@ private function reportRead(
300301

301302
/** @return array{0: CacheState, 1: array{hit: bool, rows: array, reason: ?string, outcome?: CacheReadOutcome}} */
302303
private function read(
303-
CachingQueryBuilder $query,
304+
QueryBuilder $query,
304305
QueryPlan $plan,
305306
string $namespace,
306307
string $queryHash,
@@ -375,7 +376,7 @@ private function read(
375376

376377
/** @return array{0: CacheState, 1: array{hit: bool, rows: array, reason: ?string, outcome?: CacheReadOutcome}} */
377378
private function readCanonicalWithResultOverlay(
378-
CachingQueryBuilder $query,
379+
QueryBuilder $query,
379380
QueryPlan $plan,
380381
string $namespace,
381382
string $queryHash,
@@ -460,7 +461,7 @@ private function readCanonicalWithResultOverlay(
460461

461462
/** @return array{0: CacheState, 1: array{hit: bool, rows: array, reason: ?string, outcome?: CacheReadOutcome}} */
462463
private function readResultOrCanonicalProjection(
463-
CachingQueryBuilder $query,
464+
QueryBuilder $query,
464465
QueryPlan $plan,
465466
string $namespace,
466467
string $queryHash,
@@ -659,7 +660,7 @@ private function readDirect(QueryPlan $plan, string $namespace, string $queryHas
659660

660661
private function epoch(): string
661662
{
662-
return $this->runtime->epoch(fn(): string => $this->store->getRaw($this->keys->epoch()) ?? '0');
663+
return $this->switch->epoch();
663664
}
664665

665666
/** @return list<stdClass>|null null on missing deleted-at column; empty array when filtered by visibility. */
@@ -753,7 +754,7 @@ private function readResultRowFallback(QueryPlan $plan): ?array
753754

754755
/** @return array{0: CacheState, 1: array{hit: bool, rows: array, reason: ?string, outcome?: CacheReadOutcome}} */
755756
private function readCanonical(
756-
CachingQueryBuilder $query,
757+
QueryBuilder $query,
757758
QueryPlan $plan,
758759
string $namespace,
759760
string $queryHash,
@@ -778,7 +779,7 @@ private function readCanonical(
778779

779780
/** @return array{0: CacheState, 1: array{hit: bool, rows: array, reason: ?string, outcome?: CacheReadOutcome}} */
780781
private function readCanonicalHead(
781-
CachingQueryBuilder $query,
782+
QueryBuilder $query,
782783
QueryPlan $plan,
783784
string $namespace,
784785
string $queryHash,
@@ -805,7 +806,7 @@ private function readCanonicalHead(
805806
* @return array{rows: array<string, stdClass>|null, outcome: CacheReadOutcome}
806807
*/
807808
private function repairRows(
808-
CachingQueryBuilder $query,
809+
QueryBuilder $query,
809810
QueryPlan $plan,
810811
CacheState $state,
811812
array $tokens,
@@ -882,7 +883,7 @@ private function releaseRepair(string $buildingKey, string $wakeKey, string $tok
882883
* @return array<string, stdClass>|null
883884
*/
884885
private function buildRepairedRows(
885-
CachingQueryBuilder $query,
886+
QueryBuilder $query,
886887
QueryPlan $plan,
887888
CacheState $state,
888889
array $tokens,
@@ -1057,7 +1058,7 @@ private function stateStillCurrent(QueryPlan $plan, CacheState $state): bool
10571058

10581059
/** @param array<int, mixed> $rows */
10591060
private function publish(
1060-
CachingQueryBuilder $query,
1061+
QueryBuilder $query,
10611062
QueryPlan $plan,
10621063
CacheState $state,
10631064
array $rows,
@@ -1081,7 +1082,7 @@ private function publish(
10811082
}
10821083

10831084
private function publishResult(
1084-
CachingQueryBuilder $query,
1085+
QueryBuilder $query,
10851086
QueryPlan $plan,
10861087
CacheState $state,
10871088
array $rows,
@@ -1136,7 +1137,7 @@ private function publishDirect(
11361137
}
11371138

11381139
private function publishCanonical(
1139-
CachingQueryBuilder $query,
1140+
QueryBuilder $query,
11401141
QueryPlan $plan,
11411142
CacheState $state,
11421143
array $rows,
@@ -1182,7 +1183,7 @@ private function resultOverlayPlan(QueryPlan $plan): QueryPlan
11821183

11831184
/** @param array<int, mixed> $rows */
11841185
private function promoteResultPayload(
1185-
CachingQueryBuilder $query,
1186+
QueryBuilder $query,
11861187
QueryPlan $resultPlan,
11871188
CacheState $sourceState,
11881189
string $namespace,
@@ -1191,7 +1192,7 @@ private function promoteResultPayload(
11911192
bool $wakeWaiters = true,
11921193
): bool {
11931194
try {
1194-
$ttl = $query->normCacheTtl() ?? $this->config->queryTtl;
1195+
$ttl = $query->configuredTtl() ?? $this->config->queryTtl;
11951196
$encoded = $this->codec->encode(
11961197
$rows,
11971198
$sourceState->epoch,
@@ -1344,7 +1345,7 @@ private function wakeTtl(): int
13441345

13451346
/** @param list<string> $dependencyHashes */
13461347
private function canonicalQueryHash(
1347-
CachingQueryBuilder $query,
1348+
QueryBuilder $query,
13481349
QueryPlan $plan,
13491350
Connection $connection,
13501351
array $dependencyHashes,
@@ -1365,12 +1366,12 @@ private function canonicalQueryHash(
13651366
}
13661367

13671368
private function declaredRoot(
1368-
CachingQueryBuilder $query,
1369+
QueryBuilder $query,
13691370
Connection $connection,
13701371
): ?TableIdentity {
13711372
$resolved = [];
13721373

1373-
foreach ($query->normCacheDependencies() as $declaration) {
1374+
foreach ($query->dependencies() as $declaration) {
13741375
$identity = $declaration->isTable()
13751376
? $this->tables->resolve($connection, $declaration->value)
13761377
: $this->dependencies->modelIdentity($connection, $declaration->value);

src/Cache/ResultRepository.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace NormCache\Cache;
44

5-
use NormCache\Database\CachingQueryBuilder;
5+
use NormCache\Database\QueryBuilder;
66
use NormCache\Payload\RawResultCodec;
77
use NormCache\Support\CacheKeyBuilder;
88
use NormCache\Support\RedisStore;
@@ -42,7 +42,7 @@ public function read(CacheState $state, mixed $raw): array
4242

4343
/** @param array<int, mixed> $rows */
4444
public function publish(
45-
CachingQueryBuilder $query,
45+
QueryBuilder $query,
4646
QueryPlan $plan,
4747
CacheState $state,
4848
array $rows,
@@ -65,7 +65,7 @@ public function publish(
6565

6666
return $this->store->publishVersionedEntries(
6767
entries: [$state->key => $encoded],
68-
ttl: $query->normCacheTtl() ?? $this->config->queryTtl,
68+
ttl: $query->configuredTtl() ?? $this->config->queryTtl,
6969
versionKeys: $versionKeys,
7070
expectedVersions: $expected,
7171
buildingKey: $lease->buildingKey,

0 commit comments

Comments
 (0)