Friday 19 June 2015

Real-time Apps with Laravel 5.1 and Event Broadcasting


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

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.


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.

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.

No comments:

Post a Comment