In this blog, 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
After all the dependencies are installed, we must set up our database.
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 calledShouldBroadcast
. 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
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' ]; } }
|
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 calledtodo-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 upresources/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 |
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