Challenge 3: Invalidate the Cache on Save — Possible Solution ==================================================================== // app/Models/Book.php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Cache; class Book extends Model { protected $fillable = ['title', 'description', 'price', 'author_id']; protected static function booted(): void { static::saved(function () { Cache::forget('book_stats'); }); static::deleted(function () { Cache::forget('book_stats'); }); } } WHY THIS IS A SAFE USE OF AN AUTOMATIC MODEL EVENT, UNLIKE CHAPTER 4'S TRAP ---------------------------------------------------------------------------- Chapter 4's gotcha warned specifically about model events that trigger a REAL, USER-VISIBLE SIDE EFFECT (sending an actual email) firing unexpectedly during seeders, factories in tests, or Tinker sessions — the concern was that an unintended action would actually happen somewhere in the outside world (an inbox receiving mail that shouldn't have been sent). Cache::forget('book_stats') has NO externally visible side effect at all. In the worst case — if it fires "too often" (during a seeder creating 50 books, for instance) — the only consequence is that the 'book_stats' cache entry gets cleared 50 times instead of once, which simply means the NEXT read of book_stats recomputes it fresh via Cache::remember() from Challenge 1. There's no email sent, no payment charged, no external API called — just a cache entry being invalidated, which is exactly the CORRECT behavior every single time a Book is saved or deleted, regardless of what code path caused that save (a real controller, a seeder, a factory, Tinker). In other words: for cache invalidation specifically, "fires on every save, no exceptions" is a FEATURE, not a bug — the cached stats SHOULD become stale and get cleared every time the underlying data changes, under any circumstances, which is precisely what an automatic model event guarantees without needing to remember to wire it into every possible code path that creates or modifies a Book. WHY THIS WORKS -------------- - static::saved() fires for BOTH creating a new Book and updating an existing one — using saved() instead of the more specific created() ensures a price correction on an EXISTING book (not just a brand new one) also correctly invalidates the stale average-price calculation. - static::deleted() covers the case Chapter 6's earlier example didn't explicitly mention — removing a book also changes the total count and average price, so it needs the same invalidation. - This is the general rule worth taking away: automatic model events are the right tool specifically when the side effect is idempotent and side-effect-free from an external perspective (cache invalidation, internal logging) — and the wrong tool when the side effect is a real-world action with consequences outside the application (sending email, charging money), which is exactly the distinction Chapter 4 drew.