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,
    ];
}
Databases in VS Code? Get DevDb

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.

Wanna chat about what you just read, or anything at all? Click here to tweet at me on 𝕏