Relationship-aware Eloquent model caching for Laravel with dependency tracking, cache tags, pagination-safe keys, and opt-in model APIs.
ghostcompiler/laravel-model-caching is a Laravel package for relationship-aware eloquent model caching for laravel with dependency tracking, cache tags, pagination-safe keys, and opt-in model apis..
It currently has 21 GitHub stars and 1.798 downloads on Packagist (latest version v1.0.0).
Install it with composer require ghostcompiler/laravel-model-caching.
Discover more Laravel packages by ghostcompiler
or browse all Laravel packages to compare alternatives.
Last updated
Relationship-aware Eloquent model caching for Laravel.
This package is designed for applications where cached parent queries must be invalidated when loaded child models change. It is not only a query cache. It stores deterministic query results and records a lightweight dependency index from model instances to cache keys.
HasModelCachingremember(), rememberForever(), dontCache(), and optional auto_remember for trait-enabled modelsget, first, firstOrFail, find, findOrFail, and pagination methodspaginate(), simplePaginate(), and cursorPaginate()composer require ghostcompiler/laravel-model-caching
Publish the config:
php artisan vendor:publish --tag=model-cache-config
Redis is recommended for production because the dependency index can use native Redis sets.
Add the trait to models that should be cacheable:
use GhostCompiler\LaravelModelCaching\Concerns\HasModelCaching;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
use HasModelCaching;
}
The trait alone does not cache queries. Either call remember() on the chain or enable auto_remember in config.
Use remember() on normal Eloquent queries:
$users = User::with('posts.comments')
->where('active', true)
->remember(60)
->get();
$tenant = User::whereHas('brand.domains', fn ($q) => $q->where('domain', $host))
->with('brand')
->remember(300)
->firstOrFail();
Enable automatic caching for all read queries on trait-enabled models:
// config/model-cache.php
'auto_remember' => true,
Then User::where('active', true)->first() is cached with default_ttl. Use dontCache() on chains that must always hit the database.
Use the convenience APIs:
$user = User::findCached(1);
$user->loadCached('posts.comments');
Disable caching on a query chain:
$users = User::remember(600)
->dontCache()
->where('active', true)
->get();
Each key includes:
These queries generate different keys:
User::with('posts')->remember(60)->get();
User::with('posts.comments')->remember(60)->get();
User::with(['posts', 'roles'])->remember(60)->get();
On a cache miss, the package stores the query result and walks the returned models plus loaded relations. It records only model primary keys in a dependency index:
model-cache:dependency:user:1 -> [cache-key-a]
model-cache:dependency:post:5 -> [cache-key-a, cache-key-b]
model-cache:dependency:comment:9 -> [cache-key-b]
When Post #5 is saved, deleted, or force deleted, only keys listed under model-cache:dependency:post:5 are forgotten. No full application cache flush is required.
When a new Post is created (or soft-deleted row is restored), every cached query rooted on that model class is forgotten too — for example paginated user lists — so new rows can appear on the next request without waiting for TTL expiry:
model-cache:dependency:user:{class-hash}:_class -> [page-1-key, page-2-key, ...]
Updates to an existing row still invalidate only that row's dependencies plus any list page that included that instance.
Cache tags are used as an extra layer when supported. Per-entry tag metadata is stored separately so entries can include result-specific tags such as post:5 while still being readable later from a deterministic query key.
Polymorphic relations are supported through the actual related models returned by Eloquent and through morph context in the cache key. If you change Laravel's morph map aliases, bump:
'morph_map_version' => 2,
This prevents old entries from colliding with new alias meanings.
Pagination methods are cached at the top-level builder operation:
$users = User::with('posts')->remember(300)->paginate(10);
The key includes the page name, current page, per-page value, and cursor state where applicable.
return [
'enabled' => true,
'default_ttl' => 3600,
'auto_remember' => false,
'store' => null,
'prefix' => 'model-cache',
'cache_tags' => true,
'dependency_ttl' => 604800,
'use_redis_sets' => true,
'auto_observe_models' => true,
'include_auth_id' => false,
'context_callbacks' => [],
'morph_map_version' => 1,
'debug' => false,
];
For multi-tenant apps, add a context callback:
'context_callbacks' => [
'tenant' => fn () => tenant('id'),
],
Warm a query:
php artisan model-cache:warm "App\Models\User" --with=posts --with=posts.comments --ttl=600 --limit=1000
Inspect keys depending on a model instance:
php artisan model-cache:inspect "App\Models\Post" 5
Flush keys depending on a model instance:
php artisan model-cache:flush "App\Models\Post" 5
Flush every key known to the package dependency index:
php artisan model-cache:flush --known
auto_remember carefully: incidental reads on cacheable models are cached until TTL or model invalidation. Use dontCache() in admin or debug paths.morph_map_version after morph map changes.rememberForever() only for data that is always invalidated by model events.composer install
composer test
This package was developed using ServBay as the local development environment.
ServBay
Mac M4Mac M4