Laravel 8.x Events and Listeners
By: Dino Cajic
Tags: Web Development, Software Development, PHP, Laravel, Programming

What is an event? In plain English, it’s “a thing that happens.” An event can be triggered whenever something occurs in your code. Laravel will say “I just finished something and I’m letting everyone listening know. Whoever wants to do something because this event was triggered, go and do it.” Imagine that a user just purchased something. You may want to:

create an invoice

notify the administrator via email

notify the administrator via slack message

notify the customer via email

notify the customer via text message

If you tried to combine each of these tasks into one controller, you would be tightly coupling your code. Most programmers agree that decoupled code is the way to go. Your invoice controller should not know how to notify the administrator; it should only know how to manage invoices.

Another example of when an even should be triggered is when a user registers for online access. You may want to send a welcome email to the user and get a slack notification letting you know that the user has subscribed.

We’re not going to tie into the authentication system in this article; I want to make it as simple as possible so we’ll do something even more basic. Whenever we visit a specific page, let’s send an email to a user. You most likely would not do this in a normal environment, but I want to completely strip out all unnecessary code.

Laravel 8.x Application

First off, create a new Laravel 8.x application. If you need help with that, you can check out either of my articles listed below.

Mailable

Next, we’ll need to create an instance of the Mailable class so that we have something to send. If you need a detailed introductory explanation on the Mailable class, check out my article on Mailables. I’ll be going through it without much additional explanation in this article.

I’ll be using MailTrap to test my code. If you haven’t already, setup a MailTrap account and add the configuration variables to your .env file.

Create a new Mailable class with the Markdown template option:

php artisan make:mail FunnyEmail -m emails.funny

This command will generate two files:

app/Mail/FunnyEmail.php

resources/views/emails/funny.blade.php

Open the FunnyEmail Mailable class and verify that it returns the emails.funny markdown template inside of the build() method.

Open the funny.blade.php markdown file. You should see something like this.

Since we want to show something funny, you can modify the markdown file to say something funny.

Let’s see what that looks like. Open your web.php routes file (routes/web.php) and create a new route; I named my funny. In the route we’re going to return a new instance of our FunnyEmail class. Make sure to import it up top.

Access that page in your browser; you may have a test domain setup with Homestead or you may be spinning up the server using php artisan serve . Whatever the case may be, you should know how to access your funny page.

You should see a page that looks similar to this:

Now that we know that that works, we can test our MailTrap settings. Let’s send an email from the same route. You should have your MailTrap settings configured by now.

We’ll first need to modify our FunnyEmail Mailable class to include the from() and subject() methods.

Modify the route to send an email and return an “Email Sent” confirmation message.

Verify that it works by visiting the route in your browser. I’m able to see the “Email Sent” message in my browser.

Going into my MailTrap account, I’m able to verify that I received the email.

Events/Listeners

Now that we can verify that an email can be sent, let’s create an Event/Listener system to accomplish the same task. You may have forgotten what the point of the system is, so let’s quickly refresh. Let’s say that a customer has placed an order for a book (we only sell Funny Books). The Orders Controller may store the order details and send an email to the customer letting him/her know that the order was placed. The Orders Controller may then send another email that shows a couple of jokes, followed by sending a Slack message to the Administrator, followed by a text-message to the customer, followed by registering the user to the newsletter, etc.

As you can quickly gauge, the Orders Controller shouldn’t know how to do anything other than managing orders. The Orders Controller should raise a flag (an event) after it’s finished storing the order and a listener that’s listening for that event should tie in and do what it needs to do, like send a funny email.

In this example, we won’t have the Orders Controller. We’ll just do it all in the /funny route.

Inside of our /funny route, we’re going to raise an event with the help of the event() global helper. The event() global helper can be accessed from anywhere in the application; it accepts an argument, which is an instance of the event. If this was a Customer Registration page, you may raise a CustomerRegistered event; if a new order has been placed, you may raise a OrderCreated event. We’re going to raise an event every time someone visits our /funny route; I’ll name it FunnyPageAccessedEvent.

So, let’s pass a new instance of the FunnyPageAccessedEvent event to the event() global helper. The FunnyPageAccessedEvent event does not exist yet.

There is a manual way to create an event and a listener and an automated way. Let’s look at both.

Manually Creating Events and Listeners

To create an event, you’ll use the make:event artisan command. We already know what we’re going to call this event: FunnyPageAccessedEvent.

php artisan make:event FunnyPageAccessedEvent

You’ll see a new file located at: app/Events/FunnyPageAccessedEvent.php. If you open it up, you’ll see a file that looks like this:

The newly registered event, FunnyPageAccessedEvent, can accept data through the constructor, such as an email address. So let’s actually pass the email address to it.

Now, any time that this event is called, the email address will be required. That means that we need to pass an email address to the instance in our funny route. It’s also a good time to import the App\Events\FunnyPageAccessedEvent at the top of the document.

So, when the user visits the funny route, the event() global helper yells to the Laravel application, “Hey, Funny Page has been accessed. Anyone that’s listening, do whatever you want with it!”

Nobody is listening to anything yet. Before we create any Listeners, let’s take a look at the FunnyPageAccessedEvent class. We initialized email and set it as a private property. If we change that to public then the Listener will have access to that public property. So let’s make that small change right now. We also don’t need the broadcastOn() method for this example, so let’s delete that too. Your FunnyPageAccessedEvent class should now look like this.

Time to create a Listener. We’ll use the make:listener artisan command.

php artisan mke:listener SendFunnyEmailListener

You will see that a new file has been created: app/Listeners/SendFunnyEmailListener.php.

Open that file and you’ll see your newly created listener.

We’ll want to send an email to our visitor. We can copy the same Mail::to() code that we initially created when we tested our Mailable class, and paste it into our handle() method.

Mail::to('dinocajic+test@gmail.com')->send(new FunnyEmail());

Make sure to import the Mail facade and the FunnyEmail Mailable class.

If you look at the handle() method, it receives the Event as an argument. So, we can access our public $email property of the FunnyPageAccessedEvent through the $event argument. Let’s modify the hardcoded email and instead receive the email that’s passed to the Event.

Great. We now have an event that’s “dispatched” in the funny route and a listener, but the listener is just existing; it’s not listening for this event.

The last phase of this is to make sure that the SendFunnyEmailListener is actually listening for the FunnyPageAccessedEvent. This is done through the EventServiceProvider, which is located in app/Providers/EventServiceProvider.php. Your EventServiceProvider should look like this.

You’ll see the $listen array property. You’ll have to create a mapping for each event/listener. In this example, we have one event and one listener. Add the event and the listener to the $listen array:

Let’s recap.

The user visits the funny route. The callback function inside of the funny route calls the event() global helper, which “dispatches” the event. The Event that’s dispatched is the FunnyPageAccessedEvent. It receives the email address as a string. During instantiation of the FunnyPageAccessedEvent, the email argument is assigned to the $email public property inside of the FunnyPageAccessedEvent class. The Listener SendFunnyEmailListener is listening for the FunnyPageAccessedEvent. The way that it knows to listen for this particular event is due to the mapping that was done in the EventServiceProvider class. The SendFunnyEmailListener hears the FunnyPageAccessedEvent event. The handle() method inside of the SendFunnyEmailListener is called and the FunnyPageAcessedEvent event is passed as an argument to the handle() method. The handle() method now has access to the $email property from the FunnyPageAccessedEvent event. The Mail Facade is called and the to() method accepts the $email address from the FunnyPageAccessedEvent->$email property. The Mail Facade also calls the send() method and passes the FunnyEmail Mailable class. The FunnyEmail Mailable class returns the markdown template and the from and subject fields. The Mail facade sends the email to the specified email address.

Let’s see if it worked. Refresh the funny route in your browser and check your MailTrap inbox. I was able to successfully receive the email.

Let’s create three more listeners. We’re only going to simulate that the actions have taken place, and not actually implement this. Create the following Listeners:

SendFunnySlackMessageToAdminListener

SendFunnyTextMessageToVisitorListener

SignVisitorUpForNewsletter

php artisan make:listener SendFunnySlackMessageToAdminListener php artisan make:listener SendFunnyTextMessageToVisitorListener php artisan make:listener SignVisitorUpForNewsletter

Open each Listener (from app/Listeners) and add the following:

Again, the Listeners exist but they’re not actually listening. Let’s make sure that they’re listening for when the FunnyPageAccessedEvent is dispatched. Open the EventServiceProvider class and add the three new listeners to the FunnyPageAccessedEvent array. Make sure to import each Listener at the top.

Refresh your funny page and see what happens. You should see a page that looks like this:

Check MailTrap and verify that the email was actually sent.

We can conclude that all Listeners that were mapped in the EventServiceProvider class actually fired.

The Automatic Way

You don’t have to go through all of the commands that we outlined above. You could just visit your EventServiceProvider class and map the events and listeners before they’re created.

Let’s take the automatic approach to create this event/listener system. The event will be called NewCustomerRegisteredEvent and will be triggered each time that a new customer registers for a new account. We’ll also want to create three new listeners:

EmailCustomerNewAccountDetailsListener

SlackAdminNewAccountRegisteredListener

SignCustomerUpForDiscountsListener

Open your EventServiceProvider class and add them to your $listen property. Make sure to add the full namespace: \App\Events for events, and \App\Listeners for listeners.

Next, with artisan run the event:generate command. The event:generate command looks at the EventServiceProvider class and generates all of the Events and Listeners that it doesn’t find.

php artisan event:generate

Check your app/Events and app/Listeners folders. All of your files should be there.

You can now modify those file like you did before. If you need to add a new listener by itself, you can just add it to the EventServiceProvider class and run the event:generate artisan command. Let’s say that we want to send the customer a text message; we’ll first add the new Listener class name to the EventServiceProvider.

Run php artisan event:generate and verify that the new listener is inside of your app/Listeners folder.

To read further details about Events and Listeners, visit the Laravel documentation.