Installation:
composer require watson/rememberable
Add the trait to your base model (e.g., app/Models/Model.php or app/Models/Concerns/Rememberable.php):
use Watson\Rememberable\Rememberable;
class Model extends Illuminate\Database\Eloquent\Model
{
use Rememberable;
}
Or apply it selectively to specific models.
First Use Case:
Cache a query result for a model relationship (e.g., posts for a User):
$user = User::find(1);
$cachedPosts = $user->remember(now()->addHour())->posts()->get();
The query will now be cached for 1 hour.
Where to Look First:
Rememberable trait internals).Caching Relationships: Cache eager-loaded relationships to avoid N+1 queries:
$user = User::with(['posts' => function ($query) {
$query->remember(now()->addDays(7));
}])->find(1);
Conditional Caching: Dynamically enable caching based on logic (e.g., skip caching for admin users):
$user = User::find(1);
if (!$user->isAdmin()) {
$user->remember(now()->addHour())->posts()->get();
} else {
$user->posts()->fresh()->get(); // Bypass cache
}
Global Defaults:
Set a default cache duration in config/cache.php or via a model’s boot() method:
class User extends Model
{
protected static $rememberDuration = now()->addDays(1);
use Rememberable;
}
Now all queries on User will cache for 1 day unless overridden.
Scopes and Local Scopes: Cache scoped queries:
class User extends Model
{
public function scopeActive($query)
{
return $query->remember(now()->addMinutes(30))->where('active', true);
}
}
Manual Cache Invalidation: Invalidate cache for a specific model or relationship:
$user = User::find(1);
$user->posts()->remember(now()->addHour())->get();
// Later...
$user->posts()->remember()->get(); // Forces fresh query
API Endpoints: Cache responses for read-heavy endpoints (e.g., dashboards):
Route::get('/dashboard', function () {
return User::find(auth()->id())
->remember(now()->addMinutes(15))
->posts()
->with('comments')
->get();
});
Scheduled Jobs: Use caching to reduce database load during off-peak hours:
// In a scheduled job
$popularPosts = Post::popular()->remember(now()->addDays(1))->get();
Testing: Disable caching in tests by mocking the trait or using Laravel’s cache helpers:
$this->app->singleton('cache.store', function () {
return Cache::store('array');
});
Cache Stores:
Configure the cache driver in .env (e.g., CACHE_DRIVER=redis or CACHE_DRIVER=database).
Rememberable uses Laravel’s built-in cache, so leverage existing cache tags or prefixes for organization.
Cache Tags:
Tag cached queries for bulk invalidation (e.g., Cache::tags(['users'])->remember()):
$user->remember(now()->addHour())->tag(['users'])->posts()->get();
Event Listeners: Invalidate cache on model updates:
class PostUpdatedListener
{
public function handle($event)
{
Cache::forget("rememberable:{$event->post->user_id}:posts");
}
}
Laravel Mixins (Laravel 8+):
Use mixins to add remember() to all models without modifying base classes:
Model::macro('remember', function ($duration = null) {
return (new Rememberable())->remember($duration);
});
Cache Key Collisions:
Rememberable generates keys like rememberable:{model}:{query_hash}. Ensure unique queries to avoid collisions. For complex queries, manually specify a key:
$user->remember(now()->addHour(), 'custom:key:posts')->posts()->get();
Lazy Loading Issues:
Caching relationships may interfere with lazy loading. Prefer eager loading (with()) when caching:
// Avoid this (lazy loading bypasses cache):
$user->posts->remember()->first();
Time Zone Sensitivity: Cache durations use the server’s time zone. Normalize times (e.g., UTC) for consistency:
$user->remember(now('UTC')->addHours(1))->posts()->get();
Memory Leaks:
Long-lived caches (e.g., remember(now()->addYears(1))) can bloat storage. Monitor cache size and set reasonable TTLs.
Soft Deletes:
Cached queries ignore soft deletes. Use withTrashed() or manually filter:
$user->remember()->posts()->whereNull('deleted_at')->get();
Cache Misses: Check if queries are hitting the database by enabling query logging:
DB::enableQueryLog();
$user->remember()->posts()->get();
dd(DB::getQueryLog()); // Verify no duplicate queries
Key Inspection: Inspect generated cache keys to debug issues:
$key = $user->remember()->posts()->getQuery()->getCacheKey();
dd(Cache::has($key)); // Check if key exists
Cache Store Issues: If caching fails, verify the cache driver is configured and accessible:
php artisan cache:clear
php artisan cache:table # If using database driver
Cache Warmup: Pre-load caches during deployment or via a command:
Artisan::command('cache:warmup', function () {
User::all()->remember(now()->addDays(7))->posts()->get();
});
Partial Caching: Cache only specific columns to reduce payload size:
$user->remember()->posts()->select('id', 'title')->get();
Fallback Logic: Combine caching with fallback logic for stale data:
$posts = Cache::remember("user:{$user->id}:posts", now()->addHour(), function () use ($user) {
return $user->posts()->get()->filter(fn ($post) => $post->isPublished());
});
Environment-Specific Caching: Disable caching in development:
if (app()->environment('local')) {
$user->posts()->get(); // Bypass cache
} else {
$user->remember()->posts()->get();
}
Testing Cache Behavior:
Use Cache::shouldReceive('get')->andReturn(...) in PHPUnit to mock cached responses:
Cache::shouldReceive('get')
->with('rememberable:user:1:posts')
->andReturn(collect([new Post()]));
Performance Tuning: For high-traffic apps, use a distributed cache (Redis) and monitor hit/miss ratios:
php artisan cache:config # Check cache stats
Custom Cache Tags: Extend the trait to support custom tags:
class TaggedRememberable extends Rememberable
{
public function tag($tags)
{
$this->tags = is_array($tags) ? $tags : explode(',', $tags);
return $this;
}
}
How can I help you explore Laravel packages today?