Why Laravel Queues Are Awesome

Laravel was the fastest tool I learned and I did that in a week with just a basic knowledge of PHP. Do not get me wrong but it was quite ironical to me. I saw my self as a C# master back in the days until I picked up ASP.Net and lost 15% of my weight in the first week learning it with all seriousness.

I only knew the basics of PHP before picking up Laravel and it was so simple I practiced with popcorn on my desk.

One of the reasons why it is so easy to use, is not because of the simplicity of the language it was built with which is PHP but the amount of love and care, I repeat, love and care put in to make Laravel. When I listened to Jefferey at a Laracon conference, he used the same word, care. I was questioning my subconscious on how code is written with care but by the end of his presentation I got his point.

Otwell took his time to build Laravel and it's documentation and one of the major features that really amaze me is what we are going to experiment with, Queues.

Queues in Laravel are used to make a smooth sailing application cycle by stacking heavy tasks to be handled as jobs and dispatching these jobs when it is asked to or when it does not disrupt the user's experience.

Let's assume you have a 12 year old kid named Bill and you decide to send him to a store across the road. At the same time Uncle A is asking him to get him some water and Uncle B is requesting a pen from him. That's not all; his dog Spike wants some food urgently. Bill has to complete all these tasks.

As Bill's dad/mum, if you let Bill run off to those tasks at once, he might leave some stones un-turned, get heavily exhausted or end up in an ugly mood. The best thing to do is to give him a list of those tasks and let him check them off one after the other according to priority which is obviously better. This scenario is the concept behind queues and it is good for handling heavy and tasking jobs like social media posting, email sending, heavy logging, etc.

I will take you through the simple steps of using queues by creating a demo. The demo will be a basic email sending app that sends email at the click of a button.

Before we hit the road, refer to Otwell''s documentation for Laravel installation guide.

Sending Emails in Laravel

Before we jump into queues, let us send emails the normal way, calculate the time taken and then send later with queues and compare the time difference. Speaking of Laravel being simple, if you have never sent a mail with Laravel you are going to be amazed how dead simple it is.

What we need first is to setup a mail provider for development. A service known as Mailtrap is available for us to use. Mailtrap has a free package that would be just enough for us to work with. Create an account and a sample inbox named Demo Inbox will be created for you. Click on the Demo Inbox and copy the username and password provided to your .env as follows:

MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=[USERNAME]
MAIL_PASSWORD=[PASSWORD]
MAIL_ENCRYPTION=null
MAIL_FROM=[YOUR@EMAIL.COM]
MAIL_NAME=[YOUR-NAME]

Create a controller using the artisan command to house our index and send action methods:

php artisan make:controller HomeController

With the Home controller skeleton available, we can flesh it out by adding an index action to serve as our home page:

 public function index()
    {
        return view()->make("home.index");
    }

As you can see we are returning a view that does not exist so let us create one named in index.blade.php in resources/view/home:

<div class="container">
    <div class="main">
        <a href="/send" class="btn btn-default">Send Email</a>
    </div>
</div>

We just basically have a bootstrap styled link pointing to /send which we have yet to create.

We need 2 routes for this app, / and /send, so let us replace whatever is in the app/Http/routes.php file with:

<?php
Route::get('/', 'HomeController@index');
Route::get('/send', 'HomeController@send');

The only thing left for this section is create the send action method with logic to actually send and email. Guess what! it is just 4 lines of codes.

public function send()
    {
        Log::info("Request cycle without Queues started");
        Mail::send('email.welcome', ['data'=>'data'], function ($message) {

            $message->from('me@gmail.com', 'Christian Nwmaba');

            $message->to('chris@scotch.io');

        });
        Log::info("Request cycle without Queues finished");
    }

The Log facade at the beginning and end will help us calculate when the request was completed. The Mail facade takes 3 arguments;

  • the content of the email
  • the data to be bound the the email
  • a Closure that gives us the power to customize the email settings

You definitely need to import the following dependencies at the top of your controller:

use Log;
use Mail;

I guess you are wondering what sort of content is passed as the first argument for Mail. Mail can accept plain text or a view, and we actually passed a HTML view @ resources/views/email/welcome.blade.php:

<h3>Welcome to Scotch Queue Team</h3>

<p>This is the most patient team in the Scotch community because we like to wait for our turn</p>

<p>This made our friends love us because we always give them time to do other simple tasks before
coming back to do ours</p>

At this point you can go ahead and hit the Send button on the home page. Check your logs at storage/logs/laravel.log and note the time difference.

Sending Emails with Queues

Now to the reason for this tutorial. We are going to see how simple it is to dispatch our emails using queues. Research claims that step-wise approach of learning is better, so let us try it.

Step 1: Configure a Driver

Drivers are used to manage queues and store pending queues temporarily. You can see a driver as the task list in the Bill's illustration above. We will use database driver for now as our queues are not much but if you have a heavier application consider Amazon, Beanstalkd, Redis, IronMQ, etc. You can find a list of the available drivers in config/queue.php. To create a database driver, we just run two artisan command and that is all:

php artisan queue:table

php artisan migrate

We need to tell Laravel that we want to use database in the .env file :

QUEUE_DRIVER=database

Step 2: Create a Job

Creating a job just takes another artisan command:

php artisan make:job SendWelcomeEmail --queued

The command just like make:controller will create a skeleton for us in app/Jobs named SendWelcomeEmail. The file just has one method handle() which we will implement our send email logic.

Step 3: The handle() Method

The same exact logic we had in the show action method of the Home controller will be moved to this method and then we inject the Mailer to the method as follows:

public function handle(Mailer $mailer)
    {
        $mailer->send('email.welcome', ['data'=>'data'], function ($message) {

            $message->from('nwambachristian@gmail.com', 'Christian Nwmaba');

            $message->to('nwambachristian@gmail.com');

        });
    }

You need to import the Mailer service:

use Illuminate\Contracts\Mail\Mailer;

Step 4: Dispatching/Delaying Jobs

Now we need to actually tell our HomeController that the job should be executed but as a queue. The send method will be reduced to:

public function send()
    {
         Log::info("Request Cycle with Queues Begins");
        $this->dispatch(new SendWelcomeEmail());
        Log::info("Request Cycle with Queues Ends");
    }

NB: We can inject dependencies to the Job. If we had a user that is stored in our database whose email we need, we can do this: $this->dispatch(new SendWelcomeEmail($user)); Remember we will edit the Job's constructor to achieve the injecting.

Jobs can also be delayed to a given time as follows:

 public function send()
    {
        Log::info("Request Cycle with Queues Begins");
        $this->dispatch((new SendWelcomeEmail())->delay(60 * 5));
        Log::info("Request Cycle with Queues Ends");
    }

We are still logging to determine the time difference in completing the requests.

Step 5: Listening to Queues

The last thing to do is listen for queues and dispatch when the time is right:

php artisan queue:listen database

If you are using a different driver other than database, replace the connection option. You can also omit the database and just run php artisan queue:listen. Artisan will run against the default queue you set in the .env file.

Time Difference

From my PC which is an average developer PC, it took averagely 10.5 seconds to complete the request before we used queues.

[2016-02-23 13:43:37] local.INFO: Request cycle without Queues started 
[2016-02-23 13:43:50] local.INFO: Request cycle without Queues finished  

[2016-02-23 13:50:49] local.INFO: Request cycle without Queues started  
[2016-02-23 13:50:57] local.INFO: Request cycle without Queues finished

On the other hand it took 4.3 seconds to complete with queues

[2016-02-23 13:59:33] local.INFO: Request Cycle with Queues Begins  
[2016-02-23 13:59:37] local.INFO: Request Cycle with Queues Ends  

[2016-02-23 14:03:26] local.INFO: Request Cycle with Queues Begins  
[2016-02-23 14:03:32] local.INFO: Request Cycle with Queues Ends  

[2016-02-23 14:04:05] local.INFO: Request Cycle with Queues Begins  
[2016-02-23 14:04:10] local.INFO: Request Cycle with Queues Ends 

... and just 1 second to complete with delayed queues

[2016-02-23 14:32:26] local.INFO: Request Cycle with Queues Begins  
[2016-02-23 14:32:27] local.INFO: Request Cycle with Queues Ends  

[2016-02-23 14:33:24] local.INFO: Request Cycle with Queues Begins  
[2016-02-23 14:33:25] local.INFO: Request Cycle with Queues Ends  

[2016-02-23 14:34:04] local.INFO: Request Cycle with Queues Begins  
[2016-02-23 14:34:05] local.INFO: Request Cycle with Queues Ends  

You can see am not bluffing here, we have facts. Imagine you are to send a mail to a 1000 customers.

Using the Demo

Scotch will always give you a demo when you need one. The Demo Repository has 3 branches, a master, beforeQueues and afterQueues. Clone the repository and checkout to beforeQueues or afterQueues to preview the examples. The master branch is empty with Laravel skeleton.

Final Words

It will definitely be a good idea if you incorporate queues in your future projects for tasks like mail sending, social media sharing, social media posting, etc. With this you will be giving your users that experience they deserve and make them come back. At the same time, you'll be giving your server a nice treat.

Update (March 21, 2016): Updated Mailtrap's broken link

Chris Nwamba

Passion for instructing computers and understanding its language. Would love to remain a software engineer in my next life.