Challenge 3: Spot the Model Event Trap — Possible Solution ==================================================================== // THE TRAP namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Mail; class User extends Model { protected static function booted(): void { static::created(function (User $user) { Mail::to($user->email)->send(new \App\Mail\WelcomeEmail($user)); }); } } WHAT HAPPENS WHEN A TEST SUITE OR SEEDER CREATES INSTANCES ------------------------------------------------------------------ static::created() is an Eloquent MODEL EVENT — it fires automatically every single time a User row is successfully inserted into the database, with absolutely no distinction between "a real visitor registered through the actual sign-up form" and any other code path that happens to create a User row. Concretely: - Running `php artisan db:seed` (or a UserFactory-based seeder that creates 50 fake users to populate a local development database) would trigger 50 REAL attempts to send a welcome email — to fake, often nonexistent or randomly-generated email addresses, potentially bouncing, getting flagged as spam by the mail provider, or in the worst case, actually reaching a real inbox if a factory happens to generate an email address that's actually in use. - A feature test creating User::factory()->create() (as seen throughout this chapter's own examples and Chapter 3's testing chapter) would ALSO trigger a real email send attempt on every test run — slowing down the test suite with real network calls, and potentially exhausting a mail provider's sending quota or rate limits purely from running tests. - A Tinker session (php artisan tinker) where a developer creates a test User interactively while debugging something unrelated would trigger the same side effect, completely unintentionally. THE FIX ------- Move the welcome-email logic out of the automatic model event and into an explicitly-dispatched custom event instead — exactly the UserRegistered/SendWelcomeEmail pattern from Challenge 1: // Remove the booted() model event entirely from User.php // In the registration controller specifically: public function store(Request $request) { // ... create $user ... UserRegistered::dispatch($user); // fires ONLY here // ... } Now the welcome email only sends when a user genuinely registers through the real registration flow — seeders, factories in tests, and Tinker sessions that create User rows directly no longer trigger it, because none of those code paths call UserRegistered::dispatch(). WHY THIS WORKS -------------- - The core distinction is exactly what this chapter's comparison box highlighted: Eloquent model events (created, saving, etc.) are tied to the DATABASE OPERATION itself and fire unconditionally whenever that operation happens — they carry the same "fires everywhere, all the time" trade-off as Django's post_save signal. - A manually-dispatched custom event is tied to a SPECIFIC APPLICATION ACTION (a controller method choosing to call dispatch()) — it has no connection to the underlying database operation at all, so a factory or seeder creating the same model never triggers it, since neither of those code paths ever calls dispatch(). - This is a good general rule for any model event with a genuine real-world side effect (sending email, charging a payment, calling an external API): prefer a custom, explicitly-dispatched event over an automatic Eloquent model event, specifically because tests and seeders need to be able to create data WITHOUT triggering that side effect.