Advanced Laravel

Advanced Caching Strategies

18 min Lesson 11 of 40

Advanced Caching Strategies in Laravel

Caching is one of the most effective ways to improve application performance. Laravel provides powerful caching mechanisms including cache tags, atomic locks, decorators, and integration with CDNs. In this lesson, we'll explore advanced caching techniques for scalable applications.

Cache Tags for Organized Cache Management

Cache tags allow you to group related cache items and flush them together. This is particularly useful for invalidating related data efficiently.

// Store cache with tags Cache::tags(['users', 'premium'])->put('user:123', $user, 3600); Cache::tags(['users', 'admin'])->put('user:456', $admin, 3600); // Retrieve tagged cache $user = Cache::tags(['users', 'premium'])->get('user:123'); // Flush all cache items with a tag Cache::tags(['users'])->flush(); // Removes both users // Multiple tags Cache::tags(['products', 'category:5'])->put('products:category:5', $products, 7200);
Note: Cache tags are not supported by the file or database cache drivers. Use redis or memcached for tag support.

Atomic Locks for Race Condition Prevention

Atomic locks ensure that only one process can execute a critical section of code at a time, preventing race conditions in high-traffic applications.

use Illuminate\Support\Facades\Cache; // Basic lock usage $lock = Cache::lock('process-orders', 10); // 10 seconds if ($lock->get()) { try { // Critical section - only one process can be here $this->processOrders(); } finally { $lock->release(); } } else { // Lock could not be acquired Log::info('Another process is already processing orders'); } // Block and wait for lock $lock = Cache::lock('generate-report', 30); $lock->block(5, function () { // Wait up to 5 seconds for the lock $this->generateReport(); }); // Automatic release with callback Cache::lock('send-notifications', 10)->get(function () { $this->sendNotifications(); // Lock is automatically released after callback });

Cache Decorators Pattern

The decorator pattern allows you to wrap repository or service methods with caching logic, keeping your code clean and maintainable.

namespace App\Services; class CachedUserRepository { public function __construct( protected UserRepository $repository ) {} public function find($id) { return Cache::remember( "users:{$id}", 3600, fn() => $this->repository->find($id) ); } public function findByEmail($email) { return Cache::tags(['users'])->remember( "users:email:{$email}", 1800, fn() => $this->repository->findByEmail($email) ); } public function update($id, array $data) { $user = $this->repository->update($id, $data); // Invalidate cache Cache::forget("users:{$id}"); Cache::tags(['users'])->flush(); return $user; } public function getActive() { return Cache::tags(['users', 'active'])->remember( 'users:active', 600, fn() => $this->repository->getActive() ); } }
Tip: Use the decorator pattern to add caching to existing services without modifying their code. Bind the cached version in your service provider for easy switching between cached and non-cached implementations.

Query Result Caching

Cache database query results to reduce database load. Laravel provides elegant ways to cache Eloquent queries.

use Illuminate\Support\Facades\DB; // Cache query results $users = Cache::remember('active-users', 600, function () { return DB::table('users') ->where('status', 'active') ->get(); }); // Cache with query builder $products = Cache::remember('featured-products', 3600, function () { return Product::where('featured', true) ->with(['category', 'images']) ->get(); }); // Cache individual model $user = Cache::remember("user:{$id}", 1800, function () use ($id) { return User::with(['profile', 'roles'])->findOrFail($id); }); // Conditional caching public function getProducts($categoryId = null) { $key = $categoryId ? "products:category:{$categoryId}" : 'products:all'; return Cache::tags(['products'])->remember($key, 1800, function () use ($categoryId) { $query = Product::query(); if ($categoryId) { $query->where('category_id', $categoryId); } return $query->with('category')->get(); }); }

HTTP Response Caching

Cache entire HTTP responses to serve repeat requests instantly without executing any application logic.

// In routes/web.php Route::get('/blog/{slug}', [BlogController::class, 'show']) ->middleware('cache.headers:public;max_age=3600'); // In controller public function show($slug) { return Cache::remember("blog:{$slug}", 3600, function () use ($slug) { $post = Post::where('slug', $slug)->firstOrFail(); return view('blog.show', compact('post')); }); } // Custom middleware for response caching namespace App\Http\Middleware; class CacheResponse { public function handle($request, Closure $next, $minutes = 60) { $key = 'response:' . $request->fullUrl(); if ($cachedResponse = Cache::get($key)) { return response($cachedResponse['content']) ->withHeaders($cachedResponse['headers']); } $response = $next($request); if ($response->isSuccessful()) { Cache::put($key, [ 'content' => $response->getContent(), 'headers' => $response->headers->all(), ], $minutes * 60); } return $response; } }
Warning: Be careful when caching responses for authenticated users. Never cache personalized content at the HTTP layer. Use user-specific cache keys or exclude authenticated routes from response caching.

CDN Integration and Cache Warming

Integrate with Content Delivery Networks and implement cache warming strategies for optimal performance.

// Cache warming command namespace App\Console\Commands; use Illuminate\Console\Command; class WarmCache extends Command { protected $signature = 'cache:warm'; protected $description = 'Warm up application cache'; public function handle() { $this->info('Warming cache...'); // Warm homepage data Cache::remember('homepage:featured', 3600, function () { return Product::featured()->limit(8)->get(); }); // Warm category data $categories = Category::all(); foreach ($categories as $category) { Cache::tags(['products'])->remember( "products:category:{$category->id}", 3600, fn() => $category->products->load('images') ); } // Warm navigation menu Cache::remember('menu:main', 7200, function () { return Menu::with(['items' => function ($query) { $query->orderBy('order'); }])->where('slug', 'main')->first(); }); $this->info('Cache warming completed!'); } } // CDN cache purging use Illuminate\Support\Facades\Http; class CdnCachePurger { public function purgeUrl($url) { // CloudFlare example Http::withHeaders([ 'X-Auth-Email' => config('services.cloudflare.email'), 'X-Auth-Key' => config('services.cloudflare.key'), ])->post('https://api.cloudflare.com/client/v4/zones/' . config('services.cloudflare.zone') . '/purge_cache', [ 'files' => [$url], ]); } public function purgeAll() { Http::withHeaders([ 'X-Auth-Email' => config('services.cloudflare.email'), 'X-Auth-Key' => config('services.cloudflare.key'), ])->post('https://api.cloudflare.com/client/v4/zones/' . config('services.cloudflare.zone') . '/purge_cache', [ 'purge_everything' => true, ]); } }

Cache Performance Monitoring

Monitor cache hit rates and performance to optimize your caching strategy.

namespace App\Services; class CacheMonitor { public function recordHit($key) { Cache::increment("cache:hits:{$key}"); Cache::increment('cache:hits:total'); } public function recordMiss($key) { Cache::increment("cache:misses:{$key}"); Cache::increment('cache:misses:total'); } public function getHitRate() { $hits = Cache::get('cache:hits:total', 0); $misses = Cache::get('cache:misses:total', 0); $total = $hits + $misses; return $total > 0 ? ($hits / $total) * 100 : 0; } public function remember($key, $ttl, $callback) { $value = Cache::get($key); if ($value !== null) { $this->recordHit($key); return $value; } $this->recordMiss($key); $value = $callback(); Cache::put($key, $value, $ttl); return $value; } public function getStats() { return [ 'hit_rate' => round($this->getHitRate(), 2), 'total_hits' => Cache::get('cache:hits:total', 0), 'total_misses' => Cache::get('cache:misses:total', 0), ]; } }
Exercise 1: Create a cached repository for blog posts that uses cache tags to organize posts by category. Implement methods to retrieve posts, update cache on changes, and flush category-specific cache.
Exercise 2: Build a cache warming system that pre-loads frequently accessed data (homepage products, navigation menus, popular posts) during deployment or on a schedule.
Exercise 3: Implement atomic locks for a resource-intensive report generation feature. Ensure only one report can be generated at a time and queue additional requests gracefully.