Skip to main content

By Jesse Schutt

Events in Laravel

Events in Laravel are a useful programming convenience that allow a developer to make an announcement that something significant has happened in the program.

Once an event is raised any registered listeners are triggered and given the opportunity to act upon the event.

Think of it this way — Mom announces to the house “it’s time to go!”. Assuming her children are paying attention a series of activities begins: lights are shut off by Jack, Lauren locks the front door, Andy calls the dog in from the yard, and kids start buckling up in the car.

Any parents reading this know this is not how it actually goes, but you should get the idea. A proclamation is made (the event is dispatched) and things start happening (event listeners respond).

If you want to get the specifics on all the functionality surrounding events in Laravel, take a stroll through the documentation page. It’s really good. This article, however, is not intended to replace the docs. Instead, I wanted to share some of the ways I find myself using events on a day-to-day basis.

Understanding the types of events

The three main types of events I use are my own custom events, first-party native events, and occasionally third-party package events.

Native events are fired by the framework for things such as successful authentication, creation of Eloquent models, and during specific points of the application request cycle.

Similarly, third-party packages may initiate events allowing me to register listeners to respond.

Custom events are tied to the unique application logic, like TeacherHasCreatedNewClassroom or StudentHasCompletedAnAssignment. These are the kinds of events I tend to use the most.

Try running php artisan event:list to see all the events registered in your app!

Implementing events and event listeners

Laravel provides so many resources around events it can be confusing to navigate. It has been helpful for me to remember there are three main players in the event structure:

1. The event class

Event classes are simple PHP classes with a descriptive name. I like to use the past tense of whatever we are describing in the form of: {Actor}Has{ActionDescription}. These classes may take constructor parameters, but it isn’t required.

class StudentHasJoinedClassroom
{
	public function __construct(public Student $student)
	{
	}
}

2. The event listeners

Listeners are named similar to events but they tend to use more action-oriented names, like NotifyStudentOfNewAssignment or WelcomeStudentToNewClassroom. Some people add Listener to the end of their listener classes but I find it gets too wordy.

They have a single handle method which accepts an instance of the event that triggered it.

class WelcomeStudentToNewClassroom
{
	public function handle(StudentHasJoinedClassroom $event): void
	{
		// Do something here to welcome student
	}
}

3. Event registration

This is the key piece that maps the listeners to specific events. The simplest way to implement this is to use the EventServiceProvider which contains an array of event class names and their corresponding listeners.

protected $listen = [
	StudentHasJoinedClassroom::class => [
		WelcomeStudentToNewClassroom::class,
		AssignDefaultHomework::class,
	],
]

There are a few other methods for registering event listeners but I prefer this way because it makes it easy to open one file and see all the mappings in a single place.

Queued event listeners

By default, listeners will be processed in the order they are registered. This may be necessary depending on the situation, however, one of the neat benefits of using an event/listener approach is that the listeners may be processed asynchronously on one of Laravel’s job queues. Because there is no limit to the number of listeners an event can trigger, running them one after another will result in slower response times. If the listeners do not depend on each other we can push them onto the queue to be processed when there are available workers.

This is accomplished by adding the Illuminate\Contracts\Queue\ShouldQueue interface to the listener.

use Illuminate\Contracts\Queue\ShouldQueue;

class QueuedEventListenerExample implements ShouldQueue
{
	public function handle($event): void
	{
		//
	}
}

Assuming your queue is processing jobs, this listener will now be moved out of the primary execution, making your app return faster responses.

It’s worth mentioning that queued event listeners may be processed at any point after they are pushed to the queue, so make your handle method smart enough to handle changes in data that occurred while waiting.

If you are running your listeners synchronously, returning false from one of the listener’s handle method will stop the chain.

Model events

Up to this point we have focused on custom events but there is another type of event provided by Laravel called model events. These are handy when we need to respond to things happening with our Eloquent models. Consult the documentation for a full list of events but the basic idea is that Laravel fires events before and after the model has been created, updated, deleted, etc.

Let’s say we need to do some data-cleanup when a model is deleted, like removing classrooms, students, and assignments when a teacher deletes their account.

We could fire a custom TeacherHasBeenDeleted event and assign listeners to handle the additional deletions. Or, we could hook into the native Deleting event and run our listeners when it is fired.

Model events are the same as custom events other than Laravel triggers them without us having to.

Acting upon model events

In order to take action when model events are fired we must let Laravel know what code should be executed.

Booting callbacks

If the listener code is simple we may call a boot method on an Eloquent model that will be triggered when the connected event occurs. That looks like this:

class Teacher extends Model
{

	public static function boot()
	{
      parent::boot();

      static::deleting(function (Model $teacher) {
          // trigger deletion of related assets
      });
	}
}

Any of the model events may be registered in this fashion.

Model observers

If we have a lot of code in boot hooks it may be helpful to put the callbacks into a separate file. We can create another class called an “observer”.

class TeacherObserver
{
    public function deleting(Teacher $teacher): void
    {
        // trigger deletion of related assets
    }

And then we must register the observer in EventServiceProvider as such:

protected $observers = [
    Teacher::class => [TeacherObserver::class],
];

I prefer not to use observers because it is easy to forget about them since they are so far behind the scenes. Just looking at the Teacher model would give us no hints that there are things happening with the deleting event. We would have to know there is a registered observer.

$dispatchesEvents

Adding a $dispatchesEvents property to our model gives us the opportunity to connect a custom event class to native model events.

class Teacher extends Model
{
	protected $dispatchesEvents = [
		'deleting' => TeacherIsBeingDeleted::class,
	];
}

Once we’ve instructed Laravel to fire the TeacherIsBeingDeleted event when a Teacher model is being deleted we can use the EventServiceProvider's $listen array to trigger listeners as we would with a custom event.

Event subscribers

My preferred method for organizing model events and listeners is event subscribers. This approach combines the clarity of the EventServiceProvider , the clean abstraction of observers, and the visibility of $dispatchesEvents.

We start this by defining custom events that should be fired after the native model events. See the $dispatchesEvents example above.

Once the custom events are connected we can create a new subscriber. A subscriber contains a required subscribe method which maps event classes to methods within the class.

class TeacherSubscriber
{
	public function handleDeletionAssociatedResources(TeacherIsDeleting $event): void
	{
	   // trigger deletion of related assets
	}

	public function subscribe(Dispatcher $events): array
	{
		return [
			TeacherIsDeleting::class => 'handleDeletionOfAssociatedResources'
		];
	}
}

Lastly the subscriber is registered in the EventServiceProvider, which is typically where I look to see what events are being handled anyhow.

class EventServiceProvider extends ServiceProvider
{
	protected $subscribe = [
		TeacherSubscriber::class,
	];
}

This makes it convenient to open the Teacher model, glance at the $dispatchesEvents property to see what events we are acting on, visit the EventServiceProvider to see what is handling the events, and then to the TeacherSubscriber to get the specifics.

In conclusion

Breaking out of a linear step 1 → step 2 → step 3 mentality gives us the ability to architect applications in a more lifelike manner. Raising events combined with job queues means we can throw an event and continue with the program, while event listeners process information outside the primary flow. It’s a handy pattern to have at your disposal!

Want to read more tips and insights on working with a software development team that wants to help your organization grow for good? Sign up for our bimonthly newsletter.

By Jesse Schutt

Director of Engineering

Jesse is our resident woodworker. His signature is to find the deeper meaning in a project and the right tool for the job.