Laravel: The Magic of Events, Listeners and Jobs

On a side note, Steve Jobs was also magical

Italo Baeza Cabrera
5 min readJul 8, 2019

There are three things available in your application that can be pushed to the background and run independently from the Request-Response lifecycle: Listeners, Jobs and Notifications.

For the purpose of simplicity, I will treat Listeners and Subscribers as the same thing, since those are attached to an Event, and the only practical difference between both is that a Subscriber can listen to multiple events, while the Listener to only one.

Listeners can be queued, Jobs can be queued, and Notifications can be queued too, which is cool for those pesky SMTP services that take ages to send emails, or other processes that ain’t a walk in the park or depend on outside services where you don’t have control.

The Queue is basically translated to “do X as soon as possible, because Y pushed it”. Meanwhile, the Schedule resumes in “do X because it’s time to do it”.

Scheduled Jobs are good for doing things like nobody’s business. Cleaning stale records in your database, doing weekly digests for people who are subscribed to them, etcetera. If there is something to do in a particular moment (or interval), without depending in anything more than time to be fired, and may be a truth condition, you should use an Scheduled Job.

But, even if these can be queued, Jobs and Listeners work a little different, since their purpose is different.

Only Jobs can be scheduled

One of key differences between the Jobs, Listeners and Notifications, is that Jobs can be Scheduled, where Listeners and Notifications can not. They can be delayed, meaning, the Listener or Notification will be pushed to the queue but it will be processed later.

Since they’re bound to an Event (something happened), it’s very difficult, and almost counterproductive, to use them without an Event to listen. The handle() method of the Listeners receives automatically the Event it listens for, and this is automatically injected by the framework when the Event fires.

XKCD #583

If you try to Schedule an Event, it will fail miserably since it won’t receive the Event. The only way to do that is to manually inject the Event, and inside the event inject the instance of what you’re propagating through the Event Bus. Even if you could, it’s like driving for Uber so you can say that you are your own boss, when in reality you are lying to yourself:

$podcast = Podcast::find(2);$event = new PodcastUploaded($podcast);$this->schedule($event)->weekly(); // Now rap with me:
// F*ck the police comin' straight from the underground...

Notifications, on the other part, will receive a Notifiable instance, that by default is implemented by the User Model, unless you use the Notification facade and route it manually — this creates an AnonymousNotifiable instance behind the scenes, implementing the same Notifiable instance, that is passed down to the Notification so it doesn’t panic like when my dog finds a spider.

Listeners vs Jobs handling of Services

Another key difference between the Job and the Listener, is that the Job can receive anything at instancing, and you can type-hint your services in the handle() method to tell Laravel inject them for you.

Meanwhile, your Listeners will not. Instead of the handle() method, which is used to put the Event, you will need to type-hint whatever service the Listener may need through the __construct() method, which the framework will resolve automatically.

<?php

namespace App\Listeners;

use App\Events\PodcastUploaded;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Bus\Queueable;
class UploadListener implements ShouldQueue
{
use InteractsWithQueue,
Queueable;
/**
* Podcast Transcoder
*
* @var \AudioMagic\AudioTranscoder
*/
protected $transcoder;
/**
* Create the event listener.
*
* @param \AudioMagic\AudioTranscoder $transcoder
* @return void
*/
public function __construct(AudioTranscoder $transcoder)
{
$this->transcoder = $transcoder;
}

/**
* Handle the event.
*
* @param \App\Events\PodcastUploaded $event
* @return void
*/
public function handle(PodcastUploaded $event)
{
$transcoded = $this->transcoder->process()
->format('mp3')
->bitrate('128')
->file($event->podcast->path)
->output($event->podcast->path . '_128.mp3')
->start();
if ($transcoded->success()) {
// Do something with the transcoded file
}
// ... }
}

Chaining Everything

The wonders of Laravel, and PHP itself, is that you’re never tied to an strict and unique solution for a particular problem. You can chain multiple Jobs, Events, and Listeners — the only thing that can’t propagate onto anything is the Notification… in some excent.

Professional artistic representation of the chain

As you can see, the relations between these are very dynamic. I mean, you could set a Job that sends Notifications and fires an Event that is picked up by a Listener… you get the idea.

For example, you can chain this:

  • When the User uploads a Podcast file, we will dispatch a the ProcessPodcastJob to process it.
  • When the processing is a total success, the Job will fire the PodcastProcessed event.
  • The AddNewPodcastToHomeListener will listen to that event. It will add the new Podcast in the homepage with a cool image, description, and link.
  • Another NotifyNewPublishedPodcast will be in charge of dispatching notifications: the PodcastPublishedNotification for the User who uploaded it, and the NewSubscribedPodcastNotification for users subscribed to the podcast so they can listen it immediately.
Butterfly Effect. A single Podcast uploaded can run multiple logics in the application.

The Job fired an Event, that Event was picked up by two Listeners, and one of them used Notifications. Of course is just an example, most of this can be shrinked into one Job class, but if the code starts to grow, and you need to separate things into their own queue, this approximation can become very useful.

In a nutshell, is not necessary to make one Listener, or Job, to handle everything. The more modular is the processing, the better load your application can have, specially if you use multiple queues. The only cons to this, is that you can quickly lose track of all the chains, but hey, take note in some place so you don’t forget where they are!

--

--

Italo Baeza Cabrera

Graphic Designer graduate. Full Stack Web Developer. Retired Tech & Gaming Editor. https://italobc.com