Observers in Laravel

During development on a web project, you may need something to occur when a database table is modified. For example, you might want to send a confirmation notification to a user whenever a record is created. Or you might want to create a record in a log table whenever a record is updated or deleted. Or you might want to sanitize your data before it is sent to the database and strip out any html or script tags.

While you could do this in your controller methods, chances are that you are manipulating a table in multiple places, so you would have to maintain that code in multiple places. A more efficient way to do this is to place the code in Laravel’s built-in events.

Model Events

Laravel includes a number of events that are triggered during the lifetime of a model. These events correspond to different actions performed on a database table. These events include record creation: updating a record, saving (which is triggered as part creation or updating), and deleting.

There are actually two events that trigger for each of these events; one event occurs before the action occurs, and the other happens after it occurs. For example, the creating event fires before the record is created in the database, while the created event fires once the record has been created. The full list looks something like this:

  • Creating/Created
  • Updating/Updated
  • Saving/Saved
  • Deleting/Deleted
  • ForceDeleting/ForceDeleted
  • Restoring/Restored

Note: performing a mass update or delete (for example – User::where(‘active’, ‘=’, 0)->delete();) will not trigger an event, since the models are not actually retrieved in these cases. In this case, you might want to retrieve the records and then loop through them to perform the update/delete.

There are a few ways to tie into these events; below we’ll look at two of the most common—closures and observers.

Closures

Eloquent models have a static method defined for each event (created, updated, deleted, etc.), which can be used to register closures for each event. These closures are defined in the model’s booted method.

We’ll use the example of a simple table used to store appointments. When an appointment is created, we want to send an AppointmentNotification (which we’ll assume has already been created in the system) to the User associated with the Appointment.

use App\Notifications\AppointmentNotification;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

use App\Notifications\AppointmentNotification;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Appointment extends Model
{
    use HasFactory;

    // ***********************************
    #region Booted
    // ***********************************
    protected static function booted() {
        parent::booted();

        static::created(function (Appointment $appointment) {
            $appointment->user()->notify(new AppointmentNotification($appointment));
        });
    }
    #endregion

    // ***********************************
    #region Relationships
    // ***********************************
    public function user() {
        return $this->belongsTo(User::class);
    }
    #endregion
}

In the example above, you’ll notice that we added this line to the booted method of the appointment class:

        $appointment->user()->notify(new AppointmentNotification($appointment));

This tells Laravel that after an appointment has been created, we want to retrieve the user associated with the Appointment, then send an Appointment Notification to that user.

If you want to run the code in the background, you can register the event as queueable. This will execute the event listener in the application’s queue:

use function Illuminate\Events\queueable;

//...

static::created(queueable(function (Appointment $appointment) {
    // ...
}));

Observers

If you need to listen for multiple events on a model, you might want to group them together using an observer. An observer is an object that has methods for each of the model events, each of which receives the affected model as an argument. Observers are created with an Artisan command:

php artisan make:observer AppointmentObserver –model=User

This command creates a the new observer in the app/Observers directory. When first created, the observer will look like something like this:

use App\Models\Appointment;

class AppointmentObserver
{
    /**
     * Handle the Appointment "created" event.
     */
    public function created(Appointment $appointment): void
    {
        // ...
    }

    /**
    * Handle the Appointment "updated" event.
    */
    public function updated(Appointment $appointment): void
    {
        // ...
    }
    
    /**
    * Handle the Appointment "deleted" event.
    */
    public function deleted(Appointment $appointment): void
    {
        // ...
    }
}

We can now add our code for sending the Appointment Notification to the created method:

use App\Notifications\AppointmentNotification;
...

public function created(Appointment $appointment): void
{
    $appointment->user()->notify(new AppointmentNotification($appointment));
}

Once the observer is created, you will need to register it in the boot method of the App\Providers\EventServiceProvider class to start listening for events.

use App\Models\Appointment;
use App\Observers\AppointmentObserver;

/**

Register any events for your application.
*/
public function boot(): void
{
 Appointment::observe(AppointmentObserver::class);
}

Alternately, you can list the observers in the $observers property of the App\Providers\EventServiceProvider class.

use App\Models\Appointment;
use App\Observers\AppointmentObserver;

 /**

The model observers for your application.

@var array
*/
protected $observers = [
 Appointment::class => [AppointmentObserver::class],
];

Muting Events

Occasionally, you might need to run a command on the model without triggering any events; there are a couple ways to do this. The withoutEvents method accepts a closure as its argument; any code run inside this close will not trigger an event.

use App\Models\Appointment

$appointment = Appointment::withoutEvents(function() {
    $appointment = Appointment::findOrFail(1)->update['cancelled=>true]);
   ....
    return $appoitnment
});

Using withoutEvents is convenient if you are modifying multiple records, or performing multiple actions on a model. If you need to run a single action on a model, you can use use methods like saveQuietly, deleteQuietly, forceDeleteQuietly and restoreQuietly.

$appointment = Appointment::findOrFail(1);
$appointment->cancelled = true;
$appointment->saveQuietly();

Conclusion

We hope that this helps to clarify the role of observers in Laravel, and that you have a better idea of when and how to use them. If you have a Laravel project in mind, we would be happy to assist. Send us a note and we’ll be in touch.