轻松实现 Eloquent 模型缓存。
Automatic, self-invalidating Eloquent model and relationship caching. Add a trait to your models and all query results are cached automatically — no manual cache keys, no forgetting to invalidate. When a model is created, updated, or deleted the relevant cache entries are flushed for you.
⚡ Typical performance improvements range from 100–900% reduction in database queries on read-heavy pages. Backed by 335+ integration tests across PHP 8.2–8.5 and Laravel 11–13.
Use this package when your application makes many repeated Eloquent queries and you want a drop-in caching layer that stays in sync with your data without any manual bookkeeping.
❌ Without this package — manual cache keys, manual invalidation:
$posts = Cache::remember('posts:active:page:1', 3600, function () {
return Post::where('active', true)->with('comments')->paginate();
});
// And in every observer or event listener…
Cache::forget('posts:active:page:1');
// Hope you remembered every key variant!
✅ With this package — add the trait, query normally:
// Just query. Caching and invalidation happen automatically. ✨
$posts = Post::where('active', true)->with('comments')->paginate();
get, first, find, all, paginate, pluck, value, exists)count, sum, avg, min, max)with())with()) relationships are cached. Use with() to benefit from caching.select() clauses — custom column selections bypass the cache.flushCache() manually if needed.inRandomOrder() queries — caching is automatically disabled since results should differ each time.composer require mike-bronner/laravel-model-caching
✨ The service provider is auto-discovered. No additional setup is required.
Add the Cachable trait to your models. The recommended approach is a base
model that all other models extend:
<?php
namespace App\Models;
use GeneaLabs\LaravelModelCaching\Traits\Cachable;
use Illuminate\Database\Eloquent\Model;
abstract class BaseModel extends Model
{
use Cachable;
}
Alternatively, extend the included CachedModel directly:
<?php
namespace App\Models;
use GeneaLabs\LaravelModelCaching\CachedModel;
class Post extends CachedModel
{
// ...
}
That's it — all Eloquent queries and eager-loaded relationships on these models are now cached and automatically invalidated.
⚠️ Note: You can cache the
Usermodel — theCachabletrait does not conflict with Laravel's authentication. Just avoid using cache cool-down periods on it, and ensure user updates always go through Eloquent (not rawDB::table()queries) so cache invalidation fires correctly.
Consider a blog with posts, comments, and tags:
class Post extends BaseModel
{
public function comments()
{
return $this->hasMany(Comment::class);
}
public function tags()
{
return $this->belongsToMany(Tag::class);
}
}
// All cached automatically — the query, the eager loads, everything.
$posts = Post::with('comments', 'tags')
->where('published', true)
->latest()
->paginate(15);
When a new comment is created, the cache for Post and Comment queries is
automatically invalidated — no manual Cache::forget() calls needed.
Publish the config file:
php artisan modelCache:publish --config
This creates config/laravel-model-caching.php:
return [
'cache-prefix' => '',
'enabled' => env('MODEL_CACHE_ENABLED', true),
'use-database-keying' => env('MODEL_CACHE_USE_DATABASE_KEYING', true),
'store' => env('MODEL_CACHE_STORE'),
'fallback-to-database' => env('MODEL_CACHE_FALLBACK_TO_DB', false),
];
MODEL_CACHE_ENABLED
true
✅ Enable or disable caching globally.
MODEL_CACHE_STORE
null
Cache store name from config/cache.php. Uses the default store when not set.
MODEL_CACHE_USE_DATABASE_KEYING
true
Include database connection and name in cache keys. Important for multi-tenant or multi-database apps.
MODEL_CACHE_FALLBACK_TO_DB
false
️ When true, falls back to direct database queries if the cache backend is unavailable (e.g. Redis is down) instead of throwing an exception.
** Note:** The
cache-prefixoption is set directly in the config file (not via an environment variable). For dynamic prefixes (e.g. multi-tenant), use the per-model$cachePrefixproperty shown below.
To use a dedicated cache store for model caching, define one in
config/cache.php and reference it:
MODEL_CACHE_STORE=model-cache
DynamoDB is supported when your selected Laravel cache store uses the
dynamodb driver:
MODEL_CACHE_STORE=dynamodb-model
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_DEFAULT_REGION=us-east-1
AWS_DYNAMODB_CACHE_ENDPOINT=
AWS_DYNAMODB_CACHE_TABLE=cache
Define the store in config/cache.php using the same fields Laravel documents
for the DynamoDB cache driver:
'stores' => [
'dynamodb-model' => [
'driver' => 'dynamodb',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'table' => env('AWS_DYNAMODB_CACHE_TABLE', 'cache'),
'endpoint' => env('AWS_DYNAMODB_CACHE_ENDPOINT'),
'attributes' => [
'key' => 'key',
'value' => 'value',
'expiration' => 'expires_at',
],
],
],
If your application does not already require it, install the AWS SDK:
composer require aws/aws-sdk-php
Enable DynamoDB TTL on the table's expires_at attribute as described in the
Laravel cache docs.
Model invalidation on DynamoDB uses logical namespace versioning instead of native cache tags:
modelCache:clear rotates a package-wide namespace key.This package does not issue table scans or destructive flushes on DynamoDB.
Laravel's DynamoDB cache store writes forever() entries with a long-lived
expiration instead of a truly unbounded item. In current Laravel releases that
window is several years, which means stale DynamoDB rows are bounded but can
linger for a long time after invalidation.
Practical guidance:
expires_at.modelCache:clear did not shrink the DynamoDB table: expected. The command makes old rows unreachable; it does not physically delete every row.MODEL_CACHE_FALLBACK_TO_DB=true if you want query paths to fall back to the database during cache outages.modelCache:clear: the command now returns a non-zero exit code and prints the cache error instead of silently succeeding.For multi-tenant applications you can isolate cache entries per tenant. Set the prefix globally in config:
'cache-prefix' => 'tenant-123',
Or per-model via a property:
<?php
namespace App\Models;
use GeneaLabs\LaravelModelCaching\Traits\Cachable;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use Cachable;
protected $cachePrefix = 'tenant-123';
}
When use-database-keying is enabled (the default), cache keys automatically
include the database connection and name. This keeps cache entries separate
across connections without any extra configuration.
There are three ways to bypass caching:
1. Per-query (only affects this query chain, not subsequent queries):
$results = MyModel::disableCache()->where('active', true)->get();
2. Globally via environment:
MODEL_CACHE_ENABLED=false
3. For a block of code:
$result = app('model-cache')->runDisabled(function () {
return MyModel::get();
});
// or via the Facade
use GeneaLabs\LaravelModelCaching\Facades\ModelCache;
ModelCache::runDisabled(function () {
return MyModel::get();
});
** Tip:** Use option 1 in seeders to avoid pulling stale cached data during reseeds.
In high-traffic scenarios (e.g. frequent comment submissions) you may want to prevent every write from immediately flushing the cache. Cool-down requires two steps:
Declare the default duration on the model (this alone does nothing — it just sets the value):
<?php
namespace App\Models;
use GeneaLabs\LaravelModelCaching\Traits\Cachable;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
use Cachable;
protected $cacheCooldownSeconds = 300; // 5 minutes ⏱️
}
Activate the cool-down by calling withCacheCooldownSeconds() in your
query. This writes the cool-down window into the cache store:
// Activate using the model's default (300 seconds)
Comment::withCacheCooldownSeconds()->get();
// Or override with a specific duration
Comment::withCacheCooldownSeconds(30)->get();
Once activated, writes during the cool-do
暂无开放 Issues,或尚未同步最近议题。