Eloquent model events make it easy to run logic when a model is created, updated, or deleted. But while observers and model events can keep code clean, they can also hide important behavior and introduce unexpected side effects. In this post, we'll explore Eloquent model events, observers, change detection, and when it might be better to walk away.
What are Model Events?
Eloquent models dispatch several events throughout their lifecycle, allowing you to hook into specific points in the model’s lifecycle. This makes it easy to attach custom logic that should run when a model is created, updated, deleted, or otherwise changed.
The following events are at our disposal:
- creating, created: When a new model is being created.
- updating, updated: When an existing model is being modified and the save method is called.
- saving, saved: When a model is being created or updated.
- deleting, deleted: When a model is being deleted.
- forceDeleting, forceDeleted: When a model is being deleted using the forceDelete method. Used to permanently delete soft deleted models.
- restoring, restored: When a soft deleted model is being restored.
- replicating: When the replicate method is called on a model.
- retrieved: When an existing model is retrieved from the database.
- trashed: When a model has been soft-deleted and is moved to trash.
Notes: For create, update, save, delete, force-delete, and restore events, the -ing event is dispatched before the operation, while the corresponding -ed event is dispatched after it. Events are not dispatched when issuing a mass update or delete query.
Example of a mass update:
Closures
In Laravel you can use custom event classes to handle the logic for model events. However, in this post we’ll focus on using closures.
A closure is an anonymous function that can be executed when a particular event occurs. In Eloquent, closures provide a convenient way to define model event logic directly inside the model.
Typically, closures are defined within the booted method of your model.
In the example above we are calling the activate method from our business logic, whenever a new profile is being created. This method activates the required profile and deactivates all others.
Observers
When listening to multiple events on a given model, observers can be useful for grouping all of your event listeners into a single class. This can help keep your model clean, especially when you have several event listeners.
So, how would our previous example look using an observer?
First let’s create an observer using the following Artisan command:
This command creates our ProfileObserver and associates it to the required model.
By default the generated observer will contain the most common methods for handling the different Eloquent model events.
Let’s use the above example and put it in the ProfileObserver, we will also add a trashed listener to automatically deactivate a profile when it is soft deleted.
In this case our observer would look like the following:
Finally to set up our observer we still need to register it. We have 2 options to do this.
First we could place the ObservedBy attribute on the corresponding model.
Alternatively we could invoke an observer method for the model in the boot method of the AppServiceProvider class.
Detecting changes
Eloquent provides 3 methods to determine the state of your model. Let’s take a closer look at isDirty, isClean and wasChanged. To put this in a simple example, let’s say we have a post with status set to draft.
After changing the status to published, we can see isDirty return true on the status, while false on the title.
After saving isDirty returns false and isClean will return true.
When using wasChanged on status will now return true, while false on title since we didn’t change anything there.
When to walk away
Model events are useful, but they can also make your application's behavior less explicit. A simple Profile::create() or $post->delete() may trigger additional logic somewhere else without being obvious from the code you’re looking at.
Before reaching for an observer or model event, consider whether the logic would be clearer if it were called directly.
If the behavior represents a significant business workflow or has multiple side effects, consider whether it would be clearer to make that behavior explicit through a service or other application-level component.