craftcms/laravel-dependency-aware-cache
Laravel cache extension that tracks dependencies and automatically invalidates cached items when related keys change. Useful for keeping derived or aggregated data fresh without manual cache flushing. Designed for straightforward integration with Laravel’s cache system.
Installation:
composer require craftcms/laravel-dependency-aware-cache
No additional configuration is required—the package automatically hooks into Laravel’s Cache facade.
First Use Case:
Cache a value with a dependency (e.g., a product’s price tied to its updated_at timestamp):
use CraftCms\DependencyAwareCache\Dependency\FileDependency;
use Illuminate\Support\Facades\Cache;
// Cache product price with dependency on product file
Cache::put(
'product_123_price',
99.99,
now()->addHours(1),
new FileDependency(storage_path('app/products/123.json'))
);
// Later, when the product file changes, the cache invalidates automatically
Where to Look First:
Cache or DependencyCache (extended facade with better docblocks).TagDependency, FileDependency, or CallbackDependency in src/Dependency.TagsInvalidated event in src/Events for observing invalidations.Tag-Based Invalidation (Most Common) Tie cache keys to tags (e.g., user IDs, product categories) and invalidate by tag:
// Cache user dashboard with dependency on user ID
Cache::put('user_42_dashboard', $dashboardData, now()->addMinutes(30), new TagDependency('user_42'));
// Invalidate all caches for user 42 (e.g., in a UserObserver)
TagDependency::invalidate('user_42');
File-Based Dependencies Invalidate caches when files change (e.g., config, templates, or JSON data):
Cache::put(
'config_menu',
$menuConfig,
now()->addHours(1),
new FileDependency(config_path('menu.php'))
);
Dynamic Dependencies with Callbacks
Use CallbackDependency for runtime checks (e.g., cache invalidation when a database record changes):
Cache::put(
'active_users_count',
User::active()->count(),
now()->addMinutes(5),
new CallbackDependency(fn() => User::active()->count())
);
Combining Dependencies
Use AnyDependency or AllDependencies for complex rules:
// Invalidate if EITHER the file OR the callback changes
$dependency = new AnyDependency([
new FileDependency('path/to/file'),
new CallbackDependency(fn() => $someCondition),
]);
Cache::put('complex_key', $data, null, $dependency);
Integration with Eloquent Observers Automatically invalidate caches when models are updated:
use CraftCms\DependencyAwareCache\Dependency\TagDependency;
use Illuminate\Database\Eloquent\Model;
Model::observe(function ($model) {
if ($model->wasChanged('updated_at')) {
TagDependency::invalidate("model_{$model->getKey()}");
}
});
Cache-as-You-Go
Replace manual Cache::remember with dependency-aware caching:
// Before: Manual invalidation
$data = Cache::remember("user_{$user->id}_profile", now()->addHours(1), function() use ($user) {
return $user->profile()->first();
});
// After: Dependency-aware
Cache::put("user_{$user->id}_profile", $user->profile()->first(), now()->addHours(1), new TagDependency("user_{$user->id}"));
Bulk Invalidation Invalidate multiple tags at once (e.g., during a cache rebuild):
TagDependency::invalidate(['user_1', 'user_2', 'product_42']);
Fallback to Default Cache Use Laravel’s native cache for non-critical keys:
if ($isCritical) {
Cache::put($key, $value, $ttl, $dependency);
} else {
Cache::put($key, $value, $ttl); // No dependency
}
Leverage Events
Listen for TagsInvalidated to log or trigger side effects:
use CraftCms\DependencyAwareCache\Events\TagsInvalidated;
use Illuminate\Support\Facades\Event;
Event::listen(TagsInvalidated::class, function (TagsInvalidated $event) {
Log::info("Invalidated tags: " . implode(', ', $event->tags));
});
Custom Dependencies
Extend Dependency for domain-specific logic:
class StockLevelDependency extends Dependency
{
public function __construct(public int $productId)
{
}
public function getDependencies(): array
{
return [Stock::where('product_id', $this->productId)->value('last_updated')];
}
}
Testing Mock dependencies in tests:
$this->mock(TagDependency::class)
->shouldReceive('invalidate')
->once()
->with('user_42');
Performance Tuning
CallbackDependency in high-traffic areas (runtime checks add overhead).TagDependency for most use cases (lightweight and efficient).FileDependency sparingly (file system checks can be slow).Serialization Issues
CallbackDependency may fail silently.UnsignedSerializableClosure or ensure dependencies are serializable:
$dependency = new CallbackDependency(
new UnsignedSerializableClosure(fn() => $someData)
);
Race Conditions
Cache::tags() as a fallback for critical paths.Memory Leaks
Unexpected Invalidation
CallbackDependency or FileDependency may invalidate caches unpredictably (e.g., file timestamps changing due to permissions).TagDependency::invalidate('user_42');
Log::debug('Manually invalidated user_42 caches');
Laravel Cache Tags Conflict
Cache::tags(), invalidation may behave unexpectedly.AnyDependency to combine both:
$dependency = new AnyDependency([
new TagDependency('user_42'),
new \Illuminate\Cache\TaggedCache::TAG_PREFIX . 'user_42',
]);
Driver Limitations
Check Invalidation Triggers
Listen for TagsInvalidated events to trace invalidations:
Event::listen(TagsInvalidated::class, function ($event) {
dd($event->tags); // Debug invalidated tags
});
Verify Dependencies Ensure dependencies are correctly registered:
$cache = Cache::store();
$item = $cache->get('test_key');
// Check if dependencies are attached
dd($cache->getDependencies('test_key'));
Test Edge Cases
Default Store The package replaces the default cache store automatically. To use a specific store:
Cache::store('redis')->put($key, $value, $ttl, $dependency);
Event Dispatching
Ensure TagsInvalidated events are enabled in config/cache.php:
'events' => [
'tags_invalidated' => true,
],
Laravel 10+ Compatibility
How can I help you explore Laravel packages today?