In this tutorial I’m going to walk you through a feature I’m implementing on a new project which involves async actions and Realtime Updates in Laravel. The feature is generating a screenshot of a website when the user adds a website to the app (more info on the app coming soon).

Before we start we should make a mental picture of whats involved and how we are going to achieve it.

What we need to provide:

  1. When a website is added by the user, we need to save it (Eloquent to the rescue).
  2. On save we need to make sure there is at least a “dummy” screenshot we can display to the user, not just for the layout of the ui, but to inform the user something is happening here.
  3. We need to actually generate the screenshot.
  4. When the screenshot is generated we need to save it somewhere and notify the user.

How we will achieve this:

  1. Save the website as an Eloquent Model App\Models\Domain with a screenshot property set to a default image.
  2. Use phantomjs to generate the screenshot (via a sub process handled by a Queued job and the Symfony Process component.
  3. Use Websockets to notify the user of the new screenshot so it updates without reloading the page*.

* We will actually use a package I developed called Polycast which mimics websockets via a database and ajax long polling, but the process is the same for native websockets or services like Pusher.

Originally when I created this feature I used a full  NodeJs server package called Manet and interacted with it via Guzzle, but this seems a little more convoluted and required another service to be managed, hence the simplified approach.

The Eloquent Model

First lets take a look at the Eloquent Model in use.

As you can see its a pretty simple model right now (more features being added daily). We have a relationship to define the users of the domain, a primary user (the “owner” within the app). A screenshot url value, and when the screenshot was taken. The screenshot time isn’t important right now, but I know in the future we want to update it regularly, so adding the info now means later on I can run a queue worker to update stale screenshots.

So now we have the model, lets add some routes where the user can add and fetch a domain:

With the above code we can post to /api/v1/domains to create a domain, and send get requests to /api/v1/domains/{domain} to fetch the record.

The Async Screenshot Generation

Notice how in the above controller when we create a domain record, we “dispatch” a new App\Jobs\Domains\Screenshot job onto the Laravel Queue? By itself all this does is add the “job” onto your queue worker (im using the database driver currently, but there are other drivers which can be swapped out at will).

This allows us to return the domain record and API request status without hanging around to generate the screenshot, which is a more intensive process. The user can quickly carry on while we handle the screenshot in the background.

Lets take a look at the App\Jobs\Domains\Screenshot class:

As you can see we are creating a queued job class, which is run by the queue worker. This class has the sole job of generating the websites screenshot, and firing an updated event when it’s completed successfully.

To generate the screenshot, we are making use of the phantomjs library, there are others out there, I chose the use phantomjs for its wide-spread usage and simplicity.

Interacting with none PHP processes

Phantomjs isn’t written in PHP so we need a way to access it from within our PHP code. Enter the Symfony Process Component. This widely used and tested component abstracts system usage within PHP, to hook it up just follow the provided install instructions and create a new instance with the command to run.

As we are running the process within a queued job we are directly choosing to not catch exceptions. Why? The Laravel queue worker will catch them for us, and when it does it marks the job to be re-tried and/or fails it if it needs to. That’s why we are using the mustRun method on the process and the standard run method which wont trigger exceptions.

We do have a callback set which echos the process output, this is for live debugging, in production you could safely omit this as the exception fired will contain the needed output if a job fails.

We call phantomjs with the following system call:

phantomjs ./screenshot.js {{ domain }} path/to/web/screenshots/{{ domain }}.png

This runs phantomjs with the screenshot.js file and two arguments: url, save_path.

Lets take a look at the screenshot.js file:

This isn’t a phantomjs tutorial so ill keep it short, we check we have the url and save_path arguments, then open the url, generate the screenshot and finally save it to the provided location. When thats done we exit.

Once the command has been run we simply save the Domain model with the screenshot url, and screenshot updated time.

At this point we have screenshot generation in place, and if the user refreshes the app they will see the new screenshot, but we can’t stop there.

To turn this async queued job into a realtime notification we fire a Laravel App\Events\Domains\ScreenshotUpdated event.

This event can be listened for within the PHP application, but the real magic happens when you implement the Illuminate\Contracts\Broadcasting\ShouldBroadcast interface.

Lets take a look at the event:

Whats important here is we pass in the updated Domain model, and define what “channel” it should be broadcast on. In this case we broadcast it on a channel named by the models host property. In Laravel 5.3 we have Laravel Echo to look forward to and the ability to authenticate realtime notifications.

Because we implemented the ShouldBroadcast interface this tells Laravel to emit the event on the client side as well as on the server. Regardless of the client event system used this event will be fired on the channel domain with the name App\Events\Domains\ScreenshotUpdated and with the json representation of the domain model within its data.

The Front End

Now we can listen for this event on the client. For this app im using my own Polycast package which mimics websocket behaviour by saving the event in the database, then using ajax long polling delivering the event to the client. But we could just as easily use native websockets, Socket.io, Pusher or any other alternative.

Lets take a look at the front end blade templates used to display the ui (im using Vuejs and MDL lite, but the same applies to any other methods, vanilla Html/Js, React, etc).

To get notified of the event emitted by the app we create an instance of Polycast, then within the Domain Vuejs instance’s ready method we fetch the domain via the API.

Once we have the domains current state we use it within the ui.

Then we subscribe the domain channel.

And finally within that channel we listen for the event App\Events\Domains\ScreenshotUpdated. When this happens we update the Vuejs instance data.domain.screenshot property with the screenshot value provided by the notification. Our view data is now in-sync with our server data and the UI will update to reflect the change, for the user this translates to the background image of the domain tile updating to a screenshot of their website.

All that’s left to do run the queue worker, and get users using your app.

The Laravel broadcasting event system is really great and gives you the ability to notify the front end of pretty much anything you want.

This isn’t NodeJs realtime and async, but it gives you pretty much the same result without the need to learn a new language.