Caching Strategies in Laravel
Caching is one of the most effective ways to improve application performance. Laravel provides a unified API for various caching backends, making it easy to store and retrieve data quickly, reducing database queries and expensive computations.
Cache Drivers
Configure your cache driver in .env:
# File cache (default, good for development)
CACHE_DRIVER=file
# Array cache (stored in memory, only lasts for request)
CACHE_DRIVER=array
# Database cache
CACHE_DRIVER=database
# Redis cache (recommended for production)
CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
# Memcached cache
CACHE_DRIVER=memcached
MEMCACHED_HOST=127.0.0.1
MEMCACHED_PORT=11211
# DynamoDB cache
CACHE_DRIVER=dynamodb
AWS_ACCESS_KEY_ID=your-key
AWS_SECRET_ACCESS_KEY=your-secret
For database cache, create the cache table:
php artisan cache:table
php artisan migrate
Note: Redis is recommended for production due to its speed, persistence options, and advanced features. Install predis: composer require predis/predis
Storing and Retrieving Data
Basic cache operations:
use Illuminate\Support\Facades\Cache;
// Store data (forever)
Cache::put('key', 'value');
// Store with expiration (seconds)
Cache::put('key', 'value', 600); // 10 minutes
// Store with Carbon instance
Cache::put('key', 'value', now()->addMinutes(10));
// Store if key doesn't exist
Cache::add('key', 'value', 600);
// Store forever
Cache::forever('key', 'value');
// Retrieve data
$value = Cache::get('key');
// Retrieve with default value
$value = Cache::get('key', 'default');
// Retrieve with closure default
$value = Cache::get('key', function () {
return DB::table('users')->get();
});
// Retrieve and delete
$value = Cache::pull('key');
// Check if key exists
if (Cache::has('key')) {
//
}
// Check if key doesn't exist
if (Cache::missing('key')) {
//
}
Cache Remember Pattern
The most common caching pattern - retrieve or compute and store:
// Remember for specified time
$users = Cache::remember('users', 600, function () {
return DB::table('users')->get();
});
// Remember forever
$settings = Cache::rememberForever('settings', function () {
return DB::table('settings')->pluck('value', 'key');
});
// Forget (delete) cache
Cache::forget('key');
// Clear all cache
Cache::flush();
// Increment/Decrement
Cache::increment('visits');
Cache::increment('visits', 5);
Cache::decrement('credits');
Cache::decrement('credits', 3);
// Retrieve and store (atomic)
$value = Cache::remember('key', 600, function () {
return expensiveOperation();
});
Cache Tags
Group related cache items for easier management (Redis and Memcached only):
// Store with tags
Cache::tags(['users', 'admins'])->put('admin:1', $admin, 600);
Cache::tags(['users'])->put('user:1', $user, 600);
// Retrieve with tags
$admin = Cache::tags(['users', 'admins'])->get('admin:1');
// Remember with tags
$users = Cache::tags(['users'])->remember('all', 600, function () {
return User::all();
});
// Flush specific tag
Cache::tags(['users'])->flush();
// Flush multiple tags
Cache::tags(['users', 'admins'])->flush();
Warning: Cache tags are not supported by the file or database cache drivers. Only use tags with Redis or Memcached.
Atomic Locks
Prevent race conditions using atomic locks:
use Illuminate\Support\Facades\Cache;
// Basic lock
$lock = Cache::lock('process-orders', 10); // 10 second expiration
if ($lock->get()) {
// Lock acquired, process orders
processOrders();
// Release lock
$lock->release();
}
// Block until lock is available
$lock = Cache::lock('process-orders', 10);
$lock->block(5, function () {
// Lock acquired after waiting up to 5 seconds
processOrders();
});
// Auto-release with callback
Cache::lock('process-orders')->get(function () {
// Lock acquired and will auto-release
processOrders();
});
// Check lock ownership
if ($lock->owner() === 'unique-id') {
$lock->release();
}
// Force release
$lock->forceRelease();
Cache Helper Functions
Laravel provides convenient cache helper functions:
// Using cache() helper
cache(['key' => 'value'], 600); // Store
$value = cache('key'); // Retrieve
cache()->remember('users', 600, function () {
return User::all();
});
// Using global functions
cache_put('key', 'value', 600);
$value = cache_get('key');
cache_forget('key');
cache_flush();
Model Query Caching
Cache database query results effectively:
// Cache model query
$users = Cache::remember('users:active', 600, function () {
return User::where('active', true)->get();
});
// Cache single model
$user = Cache::remember("user:{$id}", 600, function () use ($id) {
return User::findOrFail($id);
});
// Cache with relationships
$posts = Cache::remember('posts:recent', 600, function () {
return Post::with('author', 'comments')
->latest()
->take(10)
->get();
});
// Cache count queries
$count = Cache::remember('users:count', 3600, function () {
return User::count();
});
// Invalidate cache on model events
class User extends Model
{
protected static function boot()
{
parent::boot();
static::saved(function () {
Cache::forget('users:active');
Cache::forget('users:count');
});
static::deleted(function () {
Cache::forget('users:active');
Cache::forget('users:count');
});
}
}
Rate Limiting with Cache
Implement rate limiting using cache:
use Illuminate\Support\Facades\RateLimiter;
// Configure rate limiter
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
// Check rate limit
if (RateLimiter::tooManyAttempts('send-message:' . $user->id, 5)) {
$seconds = RateLimiter::availableIn('send-message:' . $user->id);
return response()->json([
'message' => "Too many requests. Try again in {$seconds} seconds."
], 429);
}
// Hit rate limiter
RateLimiter::hit('send-message:' . $user->id, 60); // 60 second decay
// Custom rate limiting
$executed = RateLimiter::attempt(
'send-email:' . $user->id,
$maxAttempts = 5,
function () {
// Send email
},
$decaySeconds = 60
);
if (!$executed) {
return 'Too many emails sent!';
}
// Clear rate limiter
RateLimiter::clear('send-message:' . $user->id);
View Caching
Cache compiled Blade views for better performance:
# Cache all views
php artisan view:cache
# Clear view cache
php artisan view:clear
Cache view fragments within Blade templates:
@cache('sidebar', 600)
<div class="sidebar">
@foreach(Category::all() as $category)
<a href="{{ $category->url }}">{{ $category->name }}</a>
@endforeach
</div>
@endcache
{{-- Custom cache directive (create in service provider) --}}
Blade::directive('cache', function ($expression) {
return "<?php if(! cache()->has({$expression})):
cache()->put({$expression}, ob_start()); ?>";
});
Blade::directive('endcache', function () {
return "<?php cache()->put(
array_shift(func_get_args()),
ob_get_clean()
); endif; ?>";
});
Advanced Caching Strategies
// Cache-Aside Pattern
public function getUser($id)
{
return Cache::remember("user:{$id}", 600, function () use ($id) {
return User::find($id);
});
}
// Write-Through Pattern
public function updateUser($id, array $data)
{
$user = User::findOrFail($id);
$user->update($data);
Cache::put("user:{$id}", $user, 600);
return $user;
}
// Cache Warming
public function warmCache()
{
$popularProducts = Product::popular()->get();
Cache::put('products:popular', $popularProducts, 3600);
$categories = Category::with('products')->get();
Cache::put('categories:tree', $categories, 3600);
}
// Stale-While-Revalidate Pattern
public function getProducts()
{
$products = Cache::get('products');
if (!$products) {
$products = Product::all();
Cache::put('products', $products, 600);
}
// Revalidate in background if cache is old
if (Cache::get('products:timestamp') < now()->subMinutes(5)) {
dispatch(new RefreshProductsCache());
Cache::put('products:timestamp', now(), 600);
}
return $products;
}
// Multi-Level Caching
public function getUser($id)
{
// Level 1: Application cache (Redis)
$user = Cache::get("user:{$id}");
if (!$user) {
// Level 2: Query result cache
$user = DB::table('users')
->where('id', $id)
->remember(600)
->first();
Cache::put("user:{$id}", $user, 600);
}
return $user;
}
Tip: Use cache tagging to organize related cache entries. When a user updates their profile, flush all user-related caches: Cache::tags(['user:' . $id])->flush()
Cache Performance Monitoring
// Log cache hits and misses
Cache::spy();
Event::listen('cache:hit', function ($event) {
\Log::info('Cache hit', ['key' => $event->key]);
});
Event::listen('cache:missed', function ($event) {
\Log::info('Cache miss', ['key' => $event->key]);
});
// Monitor cache performance
$start = microtime(true);
$value = Cache::get('expensive-query');
$time = microtime(true) - $start;
if ($time > 0.1) {
\Log::warning('Slow cache retrieval', [
'key' => 'expensive-query',
'time' => $time,
]);
}
Exercise 1: Implement a comprehensive caching strategy for a blog. Cache post lists, individual posts with comments, category trees, and popular posts. Include automatic cache invalidation when posts are created, updated, or deleted.
Exercise 2: Build a rate limiter for an API using cache. Implement per-user rate limits (100 requests/hour), IP-based limits for anonymous users (20 requests/hour), and return proper 429 responses with Retry-After headers.
Exercise 3: Create a cache warming system that pre-populates cache with frequently accessed data during off-peak hours. Use scheduled tasks to refresh cache for homepage data, popular products, and navigation menus every 30 minutes.
Cache Best Practices
Best Practices:
- Use appropriate TTL: Short TTL for dynamic data (5-10 min), long TTL for static data (hours/days)
- Cache at multiple layers: HTTP cache, application cache, database query cache
- Invalidate smartly: Use tags or patterns to invalidate related caches together
- Monitor cache hit ratio: Aim for 80%+ hit ratio for effective caching
- Avoid cache stampede: Use locks when regenerating expensive cached data
- Cache serializable data: Don't cache resources or closures
- Use Redis for production: It's fast, reliable, and supports advanced features
- Warm critical caches: Pre-populate cache before traffic spikes
Effective caching is crucial for building high-performance Laravel applications. By implementing proper caching strategies, you can significantly reduce database load, improve response times, and provide a better user experience. This concludes our Laravel Framework tutorial series!