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.