Model events — creating, created, updating, updated, deleting, deleted
Concept
Eloquent fires lifecycle events at predictable points during a model's existence. The eight core events are creating, created, updating, updated, saving, saved, deleting, and deleted. The saving/saved pair fires for both inserts and updates, making them useful for cross-cutting logic. retrieving/retrieved fire when models are hydrated from query results. replicating fires when $model->replicate() is called.
Under the hood, Model::fireModelEvent() dispatches into Illuminate\Events\Dispatcher. Each event name is prefixed with eloquent.: the dispatcher emits eloquent.creating: App\Models\Post. Listeners registered in $dispatchesEvents map event names to event classes, giving you typed event objects rather than raw Eloquent events — useful when you need to hand control to your application's event system.
You can also register closures directly on the model using static::creating(fn ($model) => ...) inside booted(). This is convenient for small hooks but unscalable for complex logic. The booted() static method is called once per model class when the model is first instantiated, making it the canonical place for both global scope registration and event hook registration.
Returning false from a creating, updating, saving, or deleting observer/listener cancels the operation. This is the Eloquent equivalent of a validation gate — use it to enforce invariants before hitting the database. After false is returned, the model's fireModelEvent() returns false, and the calling method (performInsert, performUpdate, etc.) immediately returns without executing the SQL.
| Event | SQL trigger | Cancellable |
|---|---|---|
creating / created | INSERT | creating only |
updating / updated | UPDATE | updating only |
saving / saved | INSERT or UPDATE | saving only |
deleting / deleted | DELETE | deleting only |
retrieved | after SELECT hydration | no |
replicating | $model->replicate() | no |
Code Example
<?php
declare(strict_types=1);
namespace App\Models;
use App\Events\PostPublished;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class Post extends Model
{
// Map Eloquent event names to your own typed event classes
protected $dispatchesEvents = [
'created' => PostPublished::class,
];
protected static function booted(): void
{
// Hook closure — generate slug before insert
// Returning false here would cancel the INSERT
static::creating(function (Post $post): void {
if (empty($post->slug)) {
$post->slug = Str::slug($post->title);
}
});
// Automatically set updated_by on every UPDATE
static::updating(function (Post $post): void {
$post->updated_by = auth()->id();
});
// Prevent deletion of posts that have comments
static::deleting(function (Post $post): bool|void {
if ($post->comments()->exists()) {
// Returning false cancels the DELETE — no SQL is run
return false;
}
});
}
public function comments()
{
return $this->hasMany(Comment::class);
}
}
// --- Usage ---
// INSERT fires: saving → creating → [SQL] → created → saved
// PostPublished event is dispatched after created fires
$post = Post::create(['title' => 'Hello World']);
// SQL: INSERT INTO posts (title, slug, ...) VALUES ('Hello World', 'hello-world', ...)
// UPDATE fires: saving → updating → [SQL] → updated → saved
$post->title = 'Hello Laravel';
$post->save();
// SQL: UPDATE posts SET title = 'Hello Laravel', updated_by = 1 WHERE id = 1
// DELETE — returns false if comments exist, no SQL executed
$deleted = $post->delete();
// $deleted === false if post had comments, otherwise SQL: DELETE FROM posts WHERE id = 1Interview Q&A
Q: What is the difference between creating and saving in Eloquent model events, and when would you use each?
saving fires for both inserts and updates, making it the right hook for logic that must run on every write — auditing, slug generation, setting updated_by. creating fires only before an INSERT, so it is appropriate for one-time initialization logic like generating a UUID primary key, setting a default status, or recording the creator. Using saving for insert-only logic means the logic runs unnecessarily on every update, which is wasteful and can introduce bugs if the logic modifies attributes that should only be set once.
Q: How do you cancel an Eloquent operation from within a model event listener?
Return false (not null, not throwing an exception) from the listener or observer method. Eloquent's fireModelEvent() checks the return value from the event dispatcher. If any listener returns false, the dispatcher propagates that back and the model method (performInsert, performUpdate, performDeleteOnModel) detects it and aborts. The calling code (e.g., save()) will return false to the caller. Note that retrieved and saved/created/updated/deleted are post-fact events — returning false from them has no effect on the SQL that already ran.
Q: What does $dispatchesEvents do and how is it different from registering a closure in booted()?
$dispatchesEvents maps Eloquent event names to application event classes. When Eloquent fires, say, the created event, it also dispatches the mapped class via event(new PostPublished($this)). This lets your application's broader event system (listeners, subscribers, queued listeners) react without coupling them to Eloquent internals. Closures in booted() are simpler but anonymous and synchronous — they execute inline with the Eloquent operation, cannot be queued, and cannot be easily tested in isolation. $dispatchesEvents is the bridge between Eloquent's internal lifecycle and your application's decoupled event bus.