Include tenant identity in cache keys
A dashboard caches its monthly totals under dashboard:month. The first tenant warms the key; the second tenant receives the same value. The database query may be correctly scoped while the cache boundary still leaks data.
Encode the scope in the key
$key = "tenant:{$tenant->getKey()}:dashboard:{$month}:v1";
$totals = Cache::remember($key, 300, function () use ($tenant, $month) {
return $this->calculateTotals($tenant, $month);
});
This fragment assumes $tenant comes from authenticated server-side context and $month has a normalized validated format. Include other result-changing dimensions such as currency or permission scope when the same query can return different visible data for different actors.
Invalidation must use the same dimensions
When an invoice changes, invalidate or refresh the affected tenant and period. A cache key that is safe to read but impossible to identify during updates becomes a source of stale financial summaries.
Avoid using a full request URL as a convenient key if it includes secrets or uncontrolled query variations. Build a deliberate normalized key from the actual query contract.
Test two tenants with deliberately different totals, then request the dashboards in both orders. Repeat after changing one tenant's invoice and verify the other tenant's cached result remains intact. A single-tenant test cannot detect this bug. Where correctness is critical, decide whether a cached summary is suitable at all or should be labeled with its refresh time.