Caching

Laravel Intermediate/Advanced
Course 2 · Chapter 6 · Caching

⚡ Caching

Same goal as Django's cache framework — expensive or rarely-changing data shouldn't be recomputed every request. Laravel's Cache facade is the closest 1:1 parallel this whole comparison has found: Cache::remember() and Django's cache.get_or_set() are nearly identical in shape. But this chapter also has real gaps worth being honest about — Django's core ships more caching machinery than Laravel does.

Cache Stores

// config/cache.php
'default' => env('CACHE_STORE', 'database'),

redis / memcached

Real production stores — the same role Redis/Memcached play for Django's CACHES setting.

array: Testing Only

Stores cached values only in memory for the current process — never persists across separate requests, directly parallel to Django's DummyCache in intent, though array actually caches within one process rather than always missing.

🔑 The Cache Facade

use Illuminate\Support\Facades\Cache;

Cache::put('featured_book_count', 42, 300);  // cache for 300 seconds
Cache::get('featured_book_count');          // 42 — or null if expired/missing
Cache::forget('featured_book_count');       // explicit invalidation

Cache::remember(): The Closest 1:1 Parallel Yet

Django's get_or_set() vs Laravel's remember()

Django
stats = cache.get_or_set(
    "book_stats",
    compute_stats,
    timeout=600
)
Laravel
$stats = Cache::remember(
    'book_stats',
    600,
    fn() => Book::query()->selectRaw('AVG(price) as avg_price, COUNT(*) as total')->first()
);

Same three arguments in almost the same order — key, timeout (position differs), and a callback that only runs on a cache miss. Of every comparison in this whole project, this pairing is about as close as two frameworks get.

🕳️ Where Django's Core Actually Has More

Two honest gaps, not advantages — worth stating plainly rather than always finding a Laravel win:

No Built-in @cache_page Equivalent

Django's @cache_page decorator caches an entire view's response in one line. Laravel core has no direct equivalent — full-response caching typically needs a package (e.g. spatie/laravel-responsecache) or manually wrapping a controller method's return value in Cache::remember() yourself.

No Built-in {% cache %} Equivalent

Django's {% cache %} template tag caches a template fragment directly. Blade has no core directive for this — the same pattern is achieved by wrapping the relevant data (not the rendered HTML) in Cache::remember() before passing it to the view.

// The Laravel-native way to approximate Django's @cache_page for one controller method:
public function index()
{
    $books = Cache::remember('books.index', 900, fn() => Book::all());
    return view('books.index', ['books' => $books]);
}
// Caches the DATA, not the rendered response — close, but not identical to
// Django's whole-response caching, which also skips re-rendering the template.

Cache Invalidation via Model Events

The exact same signal-based invalidation pattern from Django's caching chapter, using the Eloquent model events from Chapter 4:

Invalidating on Save/Delete

// app/Models/Book.php
use Illuminate\Support\Facades\Cache;

protected static function booted(): void
{
    static::saved(fn() => Cache::forget('book_stats'));
    static::deleted(fn() => Cache::forget('book_stats'));
}

Remember Chapter 4's gotcha: these model events fire on every save, including factories and seeders — for cache invalidation specifically, that's actually fine (clearing a cache entry too often is harmless, unlike sending an unwanted email), so model events are a reasonable choice here even though they weren't for side effects like emails.

💻 Coding Challenges

Challenge 1: Cache an Expensive Query

Write a controller method that caches the count and average price of all books under the key book_stats for 10 minutes using Cache::remember().

Goal: Practice the Cache::remember() pattern, the closest parallel to Django's get_or_set() in this course.

→ Solution

Challenge 2: Approximate Full-Response Caching

Write a controller method that caches the entire book list data for 15 minutes, and explain in one sentence how this differs from Django's @cache_page, which caches the full rendered response instead of just the data.

Goal: Practice recognizing the gap between Laravel's data-level caching and Django's response-level caching.

→ Solution

Challenge 3: Invalidate the Cache on Save

Add a booted() method to the Book model that clears the book_stats cache key whenever a book is saved or deleted, and explain why this is a safe use of an automatic model event, unlike Chapter 4's welcome-email trap.

Goal: Practice cache invalidation via model events, and articulate why this particular side effect is safe to run on every save.

→ Solution

⚠️ Gotcha: Invalidation Is Still the Hard Part

The same warning from Django's caching chapter applies word-for-word here: forgetting to call Cache::forget() (or not tying it to the right model event) means stale data persists until the timeout expires, with no error indicating anything is wrong — it just quietly shows outdated numbers. Separately, a Laravel-specific trap: the array cache driver only persists within a single process/request lifecycle — using it in a test to verify caching behavior across what should be two separate requests will misleadingly appear to "not cache at all," when really the driver was never meant to persist across process boundaries in the first place.

🎯 What's Next

With expensive data cached correctly, the next chapter covers a different kind of protection: Authorization — Laravel's Policies and Gates, compared to Django's built-in permission system.