Skip to content
Merged
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
11 changes: 10 additions & 1 deletion app/Models/Traits/HasUsage.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ public function featureLimits(): array
}

/**
* The `(int)` cast on the cached value is load-bearing: Laravel's
* RedisStore skips serialize()/unserialize() for is_numeric values so
* they can be incremented atomically — the side effect is that an int
* stored via `Cache::put` comes back as a string on read. Without the
* cast, the strict `: int` return type throws a TypeError under the
* Redis cache driver (production). File/array/database drivers don't
* have this optimisation and preserve the type, which is why the bug
* never surfaced in tests or local dev.
*
* @param array<int, string> $workspaceIds
*/
private function cachedPostCount(array $workspaceIds): int
Expand All @@ -74,7 +83,7 @@ private function cachedPostCount(array $workspaceIds): int
return 0;
}

return Cache::remember(
return (int) Cache::remember(
"account:{$this->id}:posts_count",
self::POST_COUNT_CACHE_TTL,
fn () => Post::whereIn('workspace_id', $workspaceIds)->count(),
Expand Down
2 changes: 1 addition & 1 deletion config/cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
|
*/

'default' => env('CACHE_STORE', 'database'),
'default' => env('CACHE_STORE', 'redis'),

/*
|--------------------------------------------------------------------------
Expand Down
19 changes: 19 additions & 0 deletions tests/Feature/Models/HasUsageTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,22 @@
// No cache entry should be written for the empty case.
expect(Cache::has("account:{$this->account->id}:posts_count"))->toBeFalse();
});

test('postCount survives a string-typed cache value (Redis serializer quirk)', function () {
// Laravel's RedisStore stores numeric values raw (not serialised) so they
// can be INCRemented atomically. The side effect: an int written via
// Cache::put comes back as a string on read. The test driver is `array`
// which preserves type, so we seed the cache with a literal string here
// to mimic what production sees and assert the return type stays int.
Workspace::factory()->create([
'account_id' => $this->account->id,
'user_id' => $this->owner->id,
]);

Cache::put("account:{$this->account->id}:posts_count", '42', 300);

$usage = $this->account->usage();

expect($usage['postCount'])->toBe(42);
expect($usage['postCount'])->toBeInt();
});
Loading