How to listen to model events in Laravel
Business requirements often require us to perform actions when certain events occur in our application, such as when a user is created or deleted. In Laravel, we can achieve this by listening to model events. In this article, we will explore the different ways to listen to model events, including using custom event classes, closures, and observers.
About Eloquent Model Events
Eloquent models in Laravel dispatch several events, allowing us to hook into various moments in a model's lifecycle. These events include retrieved
, creating
, created
, updating
, updated
, saving
, saved
, deleting
, deleted
, trashed
, forceDeleting
, forceDeleted
, restoring
, restored
, and replicating
. For example, when a new model is saved for the first time, the creating
and created
events will dispatch.
To start listening to model events, we need to define a $dispatchesEvents
property on our Eloquent model. This property maps various points of the model's lifecycle to our custom event classes. Here's an example:
namespace App\Models; use App\Events\UserDeleted; use App\Events\UserSaved; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; class User extends Authenticatable { use Notifiable; /** * The event map for the model. * * @var array<string, string> */ protected $dispatchesEvents = [ 'saved' => UserSaved::class, 'deleted' => UserDeleted::class, ]; }
Listening to Model Events using Closures
Instead of using custom event classes, we can register closures that execute when various model events are dispatched. We typically register these closures in the booted
method of our model:
namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { /** * The "booted" method of the model. */ protected static function booted(): void { static::created(function (User $user) { // ... }); } }
I prefer using closures when I need to perform a simple action in response to a model event. However, if I need to handle multiple events or perform more complex logic, I prefer using observers.
Eloquent Model Observers
If we are listening for many events on a given model, we can use observers to group all our listeners into a single class. Observer classes have method names that reflect the Eloquent events we wish to listen for. Each of these methods receives the affected model as their only argument.
To create an observer, we can use the make:observer
Artisan command:
php artisan make:observer UserObserver --model=User
This will create a new observer class in our app/Observers
directory. Here's an example of what the observer class might look like:
namespace App\Observers; use App\Models\User; class UserObserver { /** * Handle the User "created" event. */ public function created(User $user): void { // ... } /** * Handle the User "updated" event. */ public function updated(User $user): void { // ... } /** * Handle the User "deleted" event. */ public function deleted(User $user): void { // ... } }
To register an observer, we can place the ObservedBy
attribute on the corresponding model:
use App\Observers\UserObserver; use Illuminate\Database\Eloquent\Attributes\ObservedBy; #[ObservedBy([UserObserver::class])] class User extends Authenticatable { // }
Alternatively, we can manually register an observer by invoking the observe
method on the model we wish to observe:
use App\Models\User; use App\Observers\UserObserver; /** * Bootstrap any application services. */ public function boot(): void { User::observe(UserObserver::class); }
Listening to Model Events Globally
We can also listen to model events globally using the Model
class. This approach is useful when we need to perform actions that apply to all models. Here's an example:
// Listen for the "deleting" event on all models Model::deleting(function ($model) { Log::info('A model is about to be deleted:', [ 'model' => get_class($model), 'attributes' => $model->toArray(), ]); }); // Listen for the "deleted" event on all models Model::deleted(function ($model) { Log::info('A model has been deleted:', [ 'model' => get_class($model), 'attributes' => $model->toArray(), ]); });
When using this approach, we need to take care to exclude models that do not require event handling. We can do this by checking the model class in the event handler.
Muting Events
Sometimes, we may need to temporarily "mute" all events fired by a model. We can achieve this using the withoutEvents
method:
use App\Models\User; $user = User::withoutEvents(function () { User::findOrFail(1)->delete(); return User::find(2); });
Conclusion
As shown above, there are different ways to listen to model events in Laravel, including using custom event classes, closures, and observers; including listening to model events globally using the Model
class. We can also mute events using the withoutEvents
method. These can help in writing more robust and maintainable code, especially when responding to changes in our application's data.
Whether you prefer using closures or observers, the key is to choose the approach that best fits your needs and be consistent in your approach.