PHP
Article
By Christopher Thomas

Real-time Apps with Laravel 5.1 and Event Broadcasting

By Christopher Thomas

In Laravel 5.1, the framework includes functionality called broadcasting events that makes it easy to create real-time apps in PHP. With this new functionality, an app can publish events to various cloud-based real-time PubSub solutions, like Pusher, or to Redis.

Laravel 5.1

In this article, we examine a simple todo app and turn it into a real-time app using Laravel’s broadcasting events.

Download and Install App

The easiest way to get started is to spin up an instance of Homestead Improved. If you don’t want to use Homestead Improved, you must have git and composer installed on your system.

We will put the starter code in a directory called todo-app by cloning a git repo.

git clone https://github.com/cwt137/l51-todo-app todo-app

After the cloning is finished, go into the todo-app directory. We need to install all the app dependencies by running the following command (depending on how your composer is set up, the command might be slightly different):

composer install

After all the dependencies are installed, we must set up our database.

php artisan migrate

Testing the Non Real-time App

This app is now functional, but without the real-time functionality provided by Laravel’s broadcasting events. Open up the homepage in two browsers and put them side by side so you can do a comparison. If you do not have two different browsers, you can use two browser windows.

Manipulate the todo list in the first browser window. Then do something in the second browser window. You will notice that the other browser window that you were not in does not update without hitting the refresh button. This is because there is no real-time functionality. Let’s add some.

--ADVERTISEMENT--

Adding Real-time Capabilities to the App

Adding real-time capabilities will allow both browser windows to update their content without waiting for a hit of the refresh button.

In this example, we will define three Laravel event classes that get triggered at various times in our app. One event is the ItemCreated event that is triggered when a new item is created. The second event is the ItemUpdated event that is triggered when an item is updated (is marked completed or uncompleted). The last is the ItemDeleted event that is triggered when an item is removed from the todo list.

Broadcasting Events

To do broadcasting events, we will create a regular Laravel event, but we will implement an interface called ShouldBroadcast. If Laravel sees that an event class implements ShouldBroadcast it knows that it is a broadcasting event. This interface requires us to define a method inside our event class called broadcastOn. It should return an array of strings which are the channels that this event should be broadcasted on.

To create the event classes we need, run some artisan commands

php artisan make:event ItemCreated
php artisan make:event ItemUpdated
php artisan make:event ItemDeleted

Open up app/Events/ItemCreated.php and replace the contents with the code below:

<?php

namespace App\Events;

use App\Item;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class ItemCreated extends Event implements ShouldBroadcast
{
    use SerializesModels;

    public $id;

    /**
     * Create a new event instance.
     *
     * @param Item $item
     * @return void
     */
    public function __construct(Item $item)
    {
        $this->id = $item->id;
    }

    /**
     * Get the channels the event should be broadcast on.
     *
     * @return array
     */
    public function broadcastOn()
    {
        return ['itemAction'];
    }
}

Laravel’s event system will serialize this object and broadcast it out on the real-time cloud system using the itemAction channel. The next couple of events are similar to the first.

Open up app/Events/ItemUpdated.php and replace the contents with the code below:

<?php

namespace App\Events;

use App\Item;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class ItemUpdated extends Event implements ShouldBroadcast
{
    use SerializesModels;

    public $id;
    public $isCompleted;

    /**
     * Create a new event instance.
     *
     * @param Item $item
     * @return void
     */
    public function __construct(Item $item)
    {
        $this->id = $item->id; 
        $this->isCompleted = (bool) $item->isCompleted;
    }

    /**
     * Get the channels the event should be broadcast on.
     *
     * @return array
     */
    public function broadcastOn()
    {
        return ['itemAction'];
    }
}

Open up app/Events/ItemDeleted.php and replace the contents with the code below:

<?php

namespace App\Events;

use App\Item;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class ItemDeleted extends Event implements ShouldBroadcast
{
    use SerializesModels;

    public $id;

    /**
     * Create a new event instance.
     *
     * @param Item $item
     * @return void
     */
    public function __construct(Item $item)
    {
        $this->id = $item->id;
    }

    /**
     * Get the channels the event should be broadcast on.
     *
     * @return array
     */
    public function broadcastOn()
    {
        return ['itemAction'];
    }
}

Database Events

There are a few places in which we could fire events for our project. We could do it inside the controller, or inside the database event triggers. In this example we will use the database triggers because it feels more natural to me and doesn’t fill up the controller with extra stuff. Also, by putting the event firing logic inside the database layer, it makes it possible to fire events when a command-line program is built or when an app is built as a cron job.

Eloquent, the database library, fires events every time a model is created, saved after an update, or deleted. We will listen to those events so we can fire our own broadcasting events. This will be done through a service provider.

Open the file at app/Providers/AppServiceProvider.php and replace the contents with the following code:

<?php

namespace App\Providers;

use Event;
use App\Item;
use App\Events\ItemCreated;
use App\Events\ItemUpdated;
use App\Events\ItemDeleted;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Item::created(function ($item) {
            Event::fire(new ItemCreated($item));
        });

        Item::updated(function ($item) {
            Event::fire(new ItemUpdated($item));
        });

        Item::deleted(function ($item) {
            Event::fire(new ItemDeleted($item));
        });
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

Pusher

The real-time cloud service we will use for this example is Pusher. Support for this service is built into Laravel and is the easiest to set up.

Registration

We must register for an account to get a set of credentials. After registering on the Pusher website, go to the admin screen and create a new app called todo-app. Take note of the app_id, key and secret. We will need those later.

Pusher PHP Server Library

For our app to use Pusher, we must add the server library to our project. This is done via composer.

composer require 'pusher/pusher-php-server:2.2.1'

JavaScript

We will add some JavaScript to the bottom of our homepage. Open up resources/views/index.blade.php, the blade template for the homepage, and put the following code just before the closing body tag

<script src="//js.pusher.com/2.2/pusher.min.js"></script>
<script>
    var pusher = new Pusher("{{ env(PUSHER_KEY) }}");
</script>
<script src="js/pusher.js"></script>

The code above loads the Pusher Javascript client library, instantiates the Pusher object by passing our key into the constructor, and loads our Pusher specific app logic.

In the starter app, items were deleted and added based on events happening on the page (form submit, delete icon clicked, etc). This is why when we opened two browser windows, both did not get updated. One browser window can’t see what is going on in the other browser window. To make the app real-time, we will add and delete items based upon the events coming from Pusher. But first, open up public/js/app.js and comment out all the calls to the addItem() and removeItem() functions. Be sure not to delete the two function declarations at the top of the file.

Create the file public/js/pusher.js and put the following code into it.

( function( $, pusher, addItem, removeItem ) {

var itemActionChannel = pusher.subscribe( 'itemAction' );

itemActionChannel.bind( "App\\Events\\ItemCreated", function( data ) {

    addItem( data.id, false );
} );

itemActionChannel.bind( "App\\Events\\ItemUpdated", function( data ) {

    removeItem( data.id );
    addItem( data.id, data.isCompleted );
} );

itemActionChannel.bind( "App\\Events\\ItemDeleted", function( data ) {

    removeItem( data.id );
} );

} )( jQuery, pusher, addItem, removeItem);

The app subscribes to the itemAction channel. By default, Lavavel uses the qualified class name of the event object as the Pusher event name. The Javascript code above sets up listeners for the three events App\Events\ItemCreated, App\Events\ItemUpdated, and App\Events\ItemDeleted. There are callbacks that handle what happens when these events are triggered. As you can see the calls to getItem and removeItem were moved into the callbacks for the various events. So now, items are added or removed based on a Pusher event instead of a user event.

Testing the Realtime App

To test out this app, you need to set the keys for Pusher. By default, Laravel looks at environment variables for the Pusher information. Open up the .env file and put the following lines at the bottom.

PUSHER_KEY=YOUR_PUSHER_KEY
PUSHER_SECRET=YOUR_PUSHER_SECRET
PUSHER_APP_ID=YOUR_PUSHER_APP_ID

This will set up the variables. Where YOUR_PUSHER_KEY, YOUR_PUSHER_SECRET, YOUR_PUSHER_APP_ID are your Pusher assigned key, secret and app_id, respectively.

Open up two browser windows again and manipulate the todo list in one of them. You will see the list automatically update in the other browser window without having to hit the refresh button.

Final Thoughts

Although not covered in this article, the framework used is extensible and if Laravel does not support your real-time solution, there might be a composer package with the appropriate broadcast driver for it already. Or you can create your own broadcast driver along with a service provider to load it.

With the new broadcasting events functionality built into Laravel 5.1, it is now easier for PHP developers to create real-time apps. This new real-time capability unlocks many possibilities that were only available to apps written for other platforms like Node.js.

  • silviu

    I’m not an expert in cqrs/es but usually we save the event in a
    database(eventstore) and to the client we expose just readmodels that
    are made by projection on events. So why to expose the event back to the
    user? Thank you.

    • Xander

      because it is not CQRS and it does not do Eventsourcing, it does not build projections. The tutorial shows you how to Publish and Subscribe(http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) while leveraging events and broadcasting and the usage of Pusher as a message service. CQRS, ES and all other stuff you talk about is something else.

    • Chris

      Thanks for not only reading my article, but also commenting!

      I did not incorporate any CQRS/ES because that would just complicate the example and distract from the thesis of the article. The thesis is that with Laravel 5.1, it is now easy to build real-time apps in PHP.

      Have a data store model and a data read model? That’s a lot of work and not demonstrating what this article intended.

      There are other things that I could of done too. For example, if a user adds an item, there would be two different pieces of JS logic that would need to be implemented: 1) when a user adds an item 2) when an item is added in another web browser. To keep it simple, I just made it so nothing gets added to the page unless it comes from the Pusher event. This keeps it simple. There are a few other examples of compromises made to keep it simple.

      I leave the reader to supplement this article with knoledge of CQRS, ES, Messaging, Buses, CAP, or any other interesting concepts that might help with building a production ready, easily maintainable, enterprise grade, etc. real-time app.

  • silviu

    I guess, I can’t understand both. Are they exclusive? Or it may be work together?

    • Xander

      They are exclusive and they can be used together. They both use Events, but these are not the same events, but different in both technique and in philosophy.
      I can see you confusion. The author of this post does something weird at the end.
      The secondlast paragraph talks about “keep it simple”, then the last paragraph talks about these amazing abstract concepts that have a lot to talk about. You could write a mini book on CQRS, ES, DDD, and how to build systems with this infrastructure.

  • XCodeMonkeyX

    I find the use of the term “real time” in this article rather disturbing, as there are no guruanteed response times or anything – please check your language: http://en.wikipedia.org/wiki/Real-time_computing

  • When it comes to real-time web technologies the use of the term “real-time” is generally understood to mean soft or firm real-time.

  • Ryan Kang

    Thanks for sharing a great tutorial. Do you have a tutorial that uses Redis by any chance? I have been looking for tutorials but haven’t got any success yet.

    Thanks.

  • Jbs

    I got an error saying “Use of undefined constant PUSHER_KEY” even though I have defined it in the .env file. Any ideas?

    • Henrique Sousa

      var pusher = new Pusher(“{{ env(‘PUSHER_KEY’) }}”);

  • Mike Henken

    Wonderful tutorial! Everything worked perfect.
    Note: Make sure to rename .env.example to .env

  • Thanks for the tutorial.
    I have a question: is Pusher really necessary? why?
    All real time apps have to use a cloud service? what is that exactly?

  • Mojtaba Arbabi

    I get this error:
    FatalThrowableError in AppServiceProvider.php line 39:
    Class ‘AppItem’ not found
    how can i create Item class

  • When I add a todo, it creates two entries in a browser which I am creating in. It also create two entries when you move from completed to incomplete as well. How to fix this?

  • Tamar Danielyan

    thank you.. may I please you to share app.js code? I get Error and cannot figure out what is wrong, delete and update work great, but some problem with create.
    here:
    $( “#addFrm” ).submit(function() {
    >>>>>> $.post( “/items”, $( this ).serialize(), function( data ) {

    • Tamar Danielyan

      sorry that was because of my mistake. There was <> instead of <>..

  • Misiu

    Thanks for great article, You wrote ” By default, Lavavel uses the qualified class name of the event object as the Pusher event name. ” can this be changed? Ideally I’d like to be able to specify event name inside event class. So instead binding to “App\Events\ItemUpdated” I’d like to bind to “item.update” event.
    I’ve searched in documentation and over Stack Overflow but without any luck. Thanks for advice.

  • Syazwi Zaili

    Thanks for the tutorial.
    I have a question, I already change database from sqlite to mysql and it work fine. Now I want to develop Ionic app and get that data in real time too, how should i do that? Thank you.

    • ruther bergonia

      Maybe you can use firebase? For mobile push notifications right?

Recommended
Sponsors
Get the latest in PHP, once a week, for free.