Record Cache
This guide explains how cache works in sopheak/sp-laravel-api and where to control it.
Where to Configure
Main config is in config/record.php:
'cache' => [
'enabled' => env('SP_LARAVEL_API_CACHE_API', false),
'ttl' => env('SP_LARAVEL_API_CACHE_API_TTL', 3600),
'prefix' => 'sp_laravel_api',
'per_table' => [
// 'sp_audit_logs' => false,
],
'per_table_ttl' => [
// 'products' => 600,
],
],When Requests Are Cacheable
A request is cacheable only when all conditions are true:
record.cache.enabled = true- The table/function opts in —
disableCachedefaults totrue, so caching must be enabled explicitly per table (RecordTableType(disableCache: false)) and/or per function (RecordFunctionType(disableCache: false)) - HTTP method is
GET - Request does not include
search,filter, orwhere - Table/function cache is not disabled by config flags
Why opt-in? With caching enabled globally, every endpoint used to be cached by default, which broke client business logic that reads fresh data. Cache is now opt-in per table/function: enable it only where a cached read is safe (low-volatility data, no side effects on read).
Cache Control Levels
Global level
- Turn all cache on/off with
record.cache.enabled. - Set default TTL with
record.cache.ttl.
Table level
- Opt in per table with
RecordTableType(disableCache: false)— the defaulttruekeeps the table uncached. - Disable by table in
record.cache.per_table['table'] = false. - Override table TTL in
record.cache.per_table_ttl['table'].
Function level
- Opt in per function with
RecordFunctionType(disableCache: false)— the defaulttruekeeps the function uncached. - Override function TTL with
RecordFunctionType(cacheTTL: 300). - Clear related table caches on write with
RecordFunctionType(clearCacheTables: [...]).
Invalidation Behavior
Cache Invalidation (Namespace Versioning)
This package does not invalidate cache by wildcard deletes (no Redis KEYS, no database LIKE, and no driver-specific cache tags). Instead, it uses namespace versioning:
- Each cached key is stored with an internal version token (table/global-function + tenant).
- When a write happens (or you manually clear cache), the package increments a small namespace version key.
- New reads automatically use the latest version token, making old cached entries unreachable.
- Old entries are removed automatically when their TTL expires.
CRUD writes
For successful write operations, runtime clears affected table/record caches automatically.
Table function writes (POST|PUT|PATCH|DELETE)
- Invalidates the executed table-function cache key.
- Clears table cache for the current table by default.
- If
clearCacheTablesis set, those tables are cleared instead.
Global function writes (POST|PUT|PATCH|DELETE)
- Invalidates executed global-function cache key.
- Clears tables listed in
clearCacheTables(if provided).
Own-Records (viewOwn) and Per-User Keys
A cache entry is shared by every caller that sends the same query on the same table and tenant — the key has no user in it. Own-records scoping is the exception: when the caller is restricted by viewOwn:{pmsName} on the table, or on any table the request embeds through select / with, the key also carries the owner column(s) and the caller's user id, so each restricted user gets their own entry and never receives rows another caller cached. Callers without viewOwn keep the shared keys, so enabling it invalidates nothing. A write still invalidates every entry for the table and tenant. See Own-Records Scoping.
Manual Cache Clear
use Sopheak\Core\Services\RecordCacheService;
$cache = app(RecordCacheService::class);
$cache->clearTableCache('settings', $tenantId);
$cache->clearCacheForTables(['settings', 'users'], $tenantId);Practical Setup
- Start with
record.cache.enabled=truein production. - Opt in per table (
disableCache: false) only for low-volatility data where stale reads are safe; leave everything else at the default (uncached). - Set shorter
per_table_ttlfor near-real-time tables. - Add
clearCacheTableson write functions that mutate related tables.