ehmedp / laravel-redis-cache
A performant, scalable, and flexible Redis cache package for Laravel with tags, groups, attributes, closure caching, and artisan commands.
Requires
- php: ^8.2
- illuminate/console: ^10.0|^11.0|^12.0
- illuminate/redis: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- mockery/mockery: ^1.6
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.5|^11.0
README
A performant, scalable, and flexible Redis cache package for Laravel with tags, groups, PHP 8 attributes, closure caching, Blade directives, automatic model invalidation, and Artisan commands.
Features
- ๐ High Performance โ SCAN-based key iteration (non-blocking), Redis pipelines for batch operations
- ๐ท๏ธ Tag-based Caching โ Associate cache entries with tags, flush by tag
- ๐ Group-based Caching โ Organize cache entries into logical groups
- ๐ฏ PHP 8 Attributes โ
#[Cacheable]on classes and methods,#[CacheInvalidation]for write operations - ๐ Closure Caching โ Cache results of closures with automatic key generation
- ๐งฉ Blade Directives โ
@cache/@endcachefor caching view fragments - ๐ Auto Invalidation โ Automatic cache clearing on model create/update/delete
- ๐ Dual Strategy โ Individual (per-key) or Bulk (per-tag) invalidation
- ๐งน Artisan Commands โ Clear cache by key, tag, or flush everything
- ๐ก Events โ Hook into cache operations for monitoring and debugging
- ๐ Extensible โ Every component is interface-driven and replaceable
- ๐งช Fully Testable โ 60+ test cases covering all functionality
- ๐ฆ Laravel Auto-Discovery โ Zero configuration required
Requirements
- PHP 8.2+
- Laravel 10, 11, or 12
- Redis PHP extension (phpredis) or Predis
Installation
composer require ehmedp/laravel-redis-cache
Publish the configuration:
php artisan vendor:publish --tag=redis-cache-config
Configuration
// config/redis-cache.php return [ // Master switch โ set to false to disable all caching 'enabled' => env('REDIS_CACHE_ENABLED', true), // Redis connection from config/database.php 'connection' => env('REDIS_CACHE_CONNECTION', 'default'), // Prefix for all cache keys (e.g., "rc:users:42") 'prefix' => env('REDIS_CACHE_PREFIX', 'rc'), // Key segment separator 'separator' => ':', // Default TTL in seconds (0 = no expiration) 'default_ttl' => (int) env('REDIS_CACHE_TTL', 3600), // Tag and group prefixes 'tag_prefix' => 'tag', 'group_prefix' => 'group', // Blade @cache / @endcache directives 'blade' => [ 'enabled' => true, 'default_ttl' => null, // null = use global default_ttl ], // Cache events (CacheHit, CacheMissed, CacheWritten, etc.) 'events' => [ 'enabled' => env('REDIS_CACHE_EVENTS_ENABLED', true), ], // Automatic cache invalidation on model changes 'invalidation' => [ 'enabled' => env('REDIS_CACHE_INVALIDATION_ENABLED', true), 'default_strategy' => 'individual', // or 'bulk' 'models' => [ // Per-record caching โ only clears the specific user's cache: // \App\Models\User::class => [ // 'strategy' => 'individual', // 'tags' => ['users'], // ], // // Collection caching โ clears ALL subjects cache on any change: // \App\Models\Subject::class => [ // 'strategy' => 'bulk', // 'tags' => ['subjects'], // ], ], ], ];
Usage
Basic Operations
use EhmedP\RedisCache\Facades\RedisCache; // Put & Get RedisCache::put('user:1', $userData, 3600); $user = RedisCache::get('user:1'); $user = RedisCache::get('user:1', 'default_value'); // Check existence if (RedisCache::has('user:1')) { /* ... */ } // Remove RedisCache::forget('user:1'); // Remember (get or compute & cache) $user = RedisCache::remember('user:1', 3600, function () { return User::find(1); }); // Remember forever $settings = RedisCache::rememberForever('app:settings', function () { return Settings::all(); }); // Batch operations RedisCache::putMany(['key1' => 'val1', 'key2' => 'val2'], 3600); $values = RedisCache::many(['key1', 'key2']); // Increment / Decrement RedisCache::increment('page:views'); RedisCache::decrement('stock:item:42', 5);
Tag-based Caching
// Cache with tags RedisCache::tags('users')->put('user:1', $user, 3600); RedisCache::tags(['users', 'api'])->put('api:user:1', $data, 3600); // Remember with tags $user = RedisCache::tags('users')->remember('user:1', 3600, fn () => User::find(1)); // Flush all entries for a tag RedisCache::flushTag('users'); // Flush multiple tags RedisCache::flushTags(['users', 'api']); // Inspect keys under a tag $keys = RedisCache::getTaggedKeys('users');
Group-based Caching
$group = RedisCache::group('dashboard'); $group->put('stats', $dashboardStats, 3600); $group->put('charts', $chartData, 3600); $stats = RedisCache::group('dashboard')->remember('stats', 3600, fn () => calculateStats()); RedisCache::flushGroup('dashboard');
Closure Caching
// Auto-generated key from source location $result = RedisCache::rememberClosure(3600, function () { return heavyComputation(); }); // With arguments $result = RedisCache::rememberClosure(3600, fn($id) => User::find($id), [42]);
PHP 8 Attributes โ Method-Level
use EhmedP\RedisCache\Attributes\Cacheable; use EhmedP\RedisCache\Traits\HasCacheable; class UserService { use HasCacheable; #[Cacheable(ttl: 3600, tags: ['users'])] public function findById(int $id): ?User { return User::find($id); } } $service = new UserService(); $user = $service->cached('findById', 42); // cached! $service->flushMethodCache('findById', 42); // clear specific cache
PHP 8 Attributes โ Class-Level (Repository/Service)
Apply #[Cacheable] to a class and ALL public methods are automatically cached:
use EhmedP\RedisCache\Attributes\Cacheable; use EhmedP\RedisCache\Invalidation\InvalidationStrategy; #[Cacheable(ttl: 3600, tags: ['subjects'], invalidation: InvalidationStrategy::Bulk)] class SubjectRepository { use HasCacheable; public function all(): Collection // โ cached (from class attribute) { return Subject::all(); } public function findById(int $id): ?Subject // โ cached (from class attribute) { return Subject::find($id); } #[Cacheable(ttl: 60, tags: ['subjects', 'active'])] // โ overrides class-level public function active(): Collection { return Subject::where('active', true)->get(); } }
Cache Invalidation on Write Operations
use EhmedP\RedisCache\Attributes\CacheInvalidation; use EhmedP\RedisCache\Invalidation\InvalidationStrategy; class UserRepository { use HasCacheable; // Individual: only clears cache for the specific user ID #[CacheInvalidation( tags: ['users'], strategy: InvalidationStrategy::Individual, entityKey: 'id' )] public function update(int $id, array $data): User { return User::find($id)->update($data); } // Bulk: clears ALL "users" tagged cache #[CacheInvalidation( tags: ['users'], strategy: InvalidationStrategy::Bulk )] public function importUsers(array $data): bool { // mass import... return true; } } // Usage $repo->cachedInvalidate('update', 42, ['name' => 'John']); // clears only user:42 $repo->cachedInvalidate('importUsers', $csvData); // clears ALL users cache
Automatic Model Cache Invalidation
Configure models in config/redis-cache.php:
'invalidation' => [ 'enabled' => true, 'models' => [ // Large table: clear only the changed record \App\Models\User::class => [ 'strategy' => 'individual', 'tags' => ['users'], ], // Small lookup table: clear everything on any change \App\Models\Subject::class => [ 'strategy' => 'bulk', 'tags' => ['subjects'], ], ], ],
Add the trait to your models for convenience methods:
use EhmedP\RedisCache\Traits\HasCacheableModel; class User extends Model { use HasCacheableModel; protected array $cacheTags = ['users']; protected ?int $cacheTtl = 3600; } // Cache a model $user->cacheThis(); // Retrieve from cache $data = User::fromCache(42); // Retrieve from cache or DB (with auto-cache) $user = User::fromCacheOrFind(42); // Clear single model cache $user->clearCache(); // Clear ALL user cache User::flushModelCache();
Now when you do:
$user->update(['name' => 'New Name']); // โ ModelCacheObserver automatically clears cache for user:{id} Subject::create(['title' => 'History']); // โ ModelCacheObserver automatically clears ALL "subjects" tagged cache
Blade Directives โ @cache / @endcache
Cache rendered HTML blocks directly in your Blade templates:
{{-- Basic: cache with default TTL --}} @cache('sidebar') <div class="sidebar"> @foreach($menuItems as $item) <a href="{{ $item->url }}">{{ $item->label }}</a> @endforeach </div> @endcache {{-- With custom TTL (2 hours) --}} @cache('navigation', 7200) <nav>@include('partials.nav')</nav> @endcache {{-- With tags for targeted invalidation --}} @cache('user-profile-' . $user->id, 3600, ['users', 'profiles']) <div class="profile"> <h1>{{ $user->name }}</h1> <p>{{ $user->bio }}</p> </div> @endcache {{-- Dynamic keys with model data --}} @cache('product-card-' . $product->id, 1800, ['products']) @include('components.product-card', ['product' => $product]) @endcache
Events
Listen to cache events for monitoring, logging, or triggering side effects:
use EhmedP\RedisCache\Events\CacheHit; use EhmedP\RedisCache\Events\CacheMissed; use EhmedP\RedisCache\Events\CacheWritten; use EhmedP\RedisCache\Events\CacheFlushed; use EhmedP\RedisCache\Events\CacheInvalidated; // In EventServiceProvider or via Event::listen() Event::listen(CacheHit::class, function (CacheHit $event) { Log::debug("Cache HIT: {$event->key}"); }); Event::listen(CacheMissed::class, function (CacheMissed $event) { Log::debug("Cache MISS: {$event->key}"); }); Event::listen(CacheInvalidated::class, function (CacheInvalidated $event) { Log::info("Cache invalidated: {$event->type} -> {$event->entity}", $event->context); });
Disable events for maximum performance:
REDIS_CACHE_EVENTS_ENABLED=false
Extending โ Custom Invalidation Logic
Implement CacheInvalidatorInterface for custom behavior:
use EhmedP\RedisCache\Invalidation\CacheInvalidator; class CustomInvalidator extends CacheInvalidator { public function onUpdated(string $entity, string|int $id, array $changed = []): void { // Only invalidate if critical fields changed $criticalFields = ['name', 'email', 'role']; if (empty(array_intersect(array_keys($changed), $criticalFields))) { return; // Skip invalidation for non-critical changes } parent::onUpdated($entity, $id, $changed); } }
Bind it in your AppServiceProvider:
use EhmedP\RedisCache\Contracts\CacheInvalidatorInterface; $this->app->bind(CacheInvalidatorInterface::class, CustomInvalidator::class);
Artisan Commands
# Clear ALL cache (with confirmation) php artisan redis-cache:clear # Clear ALL cache (forced, for CI/CD) php artisan redis-cache:clear --force # Clear by tag(s) php artisan redis-cache:clear-tag users php artisan redis-cache:clear-tag users products api php artisan redis-cache:clear-tag users --force # Clear by key(s) php artisan redis-cache:clear-key user:42 php artisan redis-cache:clear-key user:42 user:43 settings:theme
Architecture
src/
โโโ Attributes/
โ โโโ Cacheable.php # Class/method caching attribute
โ โโโ CacheInvalidation.php # Write-operation invalidation attribute
โโโ Cache/
โ โโโ CacheManager.php # Main entry point (Facade root)
โ โโโ CacheStore.php # Redis store (SCAN-based, pipeline)
โ โโโ TaggedCache.php # Tag management via Redis SETs
โ โโโ CacheKeyGenerator.php # Key generation strategy
โโโ Commands/
โ โโโ ClearCacheCommand.php # php artisan redis-cache:clear
โ โโโ ClearTagCommand.php # php artisan redis-cache:clear-tag
โ โโโ ClearKeyCommand.php # php artisan redis-cache:clear-key
โโโ Contracts/
โ โโโ CacheableInterface.php # Cache store contract
โ โโโ TaggableInterface.php # Tagging contract
โ โโโ KeyGeneratorInterface.php # Key generator contract
โ โโโ CacheInvalidatorInterface.php # Invalidation contract
โโโ Events/
โ โโโ CacheHit.php # Cache key found
โ โโโ CacheMissed.php # Cache key not found
โ โโโ CacheWritten.php # Value stored in cache
โ โโโ CacheFlushed.php # Cache entries flushed
โ โโโ CacheInvalidated.php # Cache invalidated by data change
โโโ Facades/
โ โโโ RedisCache.php # Laravel Facade
โโโ Invalidation/
โ โโโ CacheInvalidator.php # Default invalidation engine
โ โโโ InvalidationStrategy.php # Individual vs Bulk enum
โ โโโ ModelCacheObserver.php # Eloquent model observer
โโโ Middleware/
โ โโโ CacheResponse.php # HTTP response caching
โโโ Support/
โ โโโ CacheGroup.php # Group management
โ โโโ ClosureSerializer.php # Closure fingerprinting
โโโ Traits/
โ โโโ HasCacheable.php # Class/method attribute caching
โ โโโ HasCacheableModel.php # Eloquent model caching
โโโ View/
โ โโโ CacheBladeDirective.php # @cache / @endcache
โโโ RedisCacheServiceProvider.php # Service Provider
Testing
composer test # or ./vendor/bin/phpunit
License
MIT License. See LICENSE for details.