Laravelは、アプリケーション独自のイベントを実装する仕組みをフレームワークとして用意しています(参照: Events)。
5.1からこのイベントシステムに、PusherおよびRedis PubSubに対応したBroadcasting Eventsが追加されました。
Broadcasting Eventsを利用したリアルタイム更新アプリのチュートリアルが、sitepointにあったので、それを試しつつLaravelのEventについて調べてみたことをまとめてみました。
今回試した記事は、「Real-time Apps with Laravel 5.1 and Event Broadcasting」になります。
実装していくのは、以下のような構成で更新をリアルタイムに通知するToDoアプリです。
新規にToDoの項目が追加されるとLaravelアプリはPusher APIを呼び出します。
Pusherはアプリを参照しているすべてのブラウザに更新を通知します。
通知を受けるとJavascriptからLaravelのAPIを呼び出し新しく追加されたToDoアイテムを表示する流れです。
Laravelの動作環境は、Laravel Homesteadを利用しました。
Homesteadの準備については「Laravel homesteadを利用したLaravel 5 ローカル開発環境の構築」も参照ください。
サンプルアプリの準備
Githubに公開されているチュートリアル用のサンプルアプリをクローンします。
こちらは、リアルタイムの機能を有効にする前の簡単なToDoアプリになります。
$ git clone https://github.com/cwt137/l51-todo-app todo-app
HomesteadのSyncfolderをクローンしたディレクトリにセットします。
ホームディレクトリにプロジェクトをクローンした場合は以下のように設定します。
# ~/.homestead/Homestead.yaml
folders:
- map: ~/todo-app
to: /home/vagrant/Code/Laravel
Homesteadを起動して、sshログインします。
$ cd <Homesteadホームディレクトリ>
$ vagrant up
$ vagrant ssh
Homesteadにログインしたら、プロジェクトディレクトリCodeでcomposer install
を実行しますが、いくつか前準備をします。
まず、composer.lockを削除
$ cd Code/Laravel
$ rm composer.lock
クローンしたcomposer.lockに含まれるext-mcryptは、現在のHomestead環境には含まれていないので、そのまま実行するとエラーになります。composer.lockは削除して、新たにcomposer installしています。
これは、Laravelは5.1から暗号化に使われるパッケージがext-mcryptからopensslに変更されていました(リリースノート Laravel 5.1 Encryptionを参照)。
このチュートリアルサンプルが安定版になる前に作成されたものなのでext-mcryptが含まれてしまっていると思われます。
同様の理由でconfig/app.phpのcipher
の値をMCRYPT_RIJNDAEL_128
からAES-256-CBC
に変更しておきます。
// title="config/app.php
'cipher' => 'AES-256-CBC',
上記2点で、準備ができたのでcomposer installを実行します。
$ composer install
Laravelはプロジェクト作成時に.env.exampleをコピーして.envを作成と暗号化キーの作成を実行しますが、クローンしたプロジェクトの場合は別途スクリプトを実行する必要があります。
$ composer run-script post-root-package-install
$ composer run-script post-create-project-cmd
データベースのマイグレーションをして、準備は完了です。
$ php artisan migrate
一旦ここで、アプリの動作を確認します。
この段階では、ブラウザを再読み込みしなければ更新は反映されません。
Broadcasting Eventsの追加
次に、更新通知をPusherに伝えるBroadcastig Eventsを追加していきます。
ToDoの追加・更新(完了チェック)・削除時にイベントを発火させるようにします。
Illuminate\Contracts\Broadcasting\ShouldBroadcast
を実装する、それぞれ以下のイベントを追加します。
- ToDoの追加: ItemCreated
- ToDoの更新: ItemUpdated
- ToDoの削除: ItemDeleted
Larvalフレームワークは、インタフェースShouldBroadcast
を実装したクラスを参照し、broadcastOn
メソッドに定義されたチャンネルごとにブロードキャストします。
上記の3つのイベントクラスを、artisanコマンドで作成します。
$ php artisan make:event ItemCreated
$ php artisan make:event ItemUpdated
$ php artisan make:event ItemDeleted
ItemCreated.phpを開き、以下のハイライトの行を追加します。
// app/Events/ItemCreated.php
<?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'];
}
}
broadcastOn
メソッドは、itemAction
というチャンネルを返すように実装しています。
ItemUpdated.phpを開き、以下のハイライトの行を追加します。
// app/Events/ItemUpdated.php" highlight="5, 10, 14,15, 23-27, 34"]
<?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'];
}
}
ItemDeleted.phpを開き、以下のハイライトの行を追加します。
// app/Events/ItemDeleted.php
<?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'];
}
}
モデルの更新時にイベントを発火する
Eloquentモデルには、レコードのCRUDイベント発生時にフックするデータベーストリガー機能があります。
参照: Events | Eloquent: Getting Started
この仕組を利用して、Itemモデルの追加時にcreated
イベント、更新時にupdated
、削除時はdeleted
イベントを受け付け、定義した各ブロードキャストイベントを発火させます。
モデルのイベントを受け付けるリスナーは、サービスプロバイダーに登録します。
ここでは、app/Providers/AppServiceProvider.php
にのboot
メソッドに以下のようにコードを追記します。
// app/Providers/AppServiceProvider.php
<?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アプリケーションの準備
Pusherでアカウントを作成して、チュートリアルサンプル用のアプリケーションを作成します。
[New App]ボタンをクリックしてアプリの作成を始めます。
以下のフォームが表示されます。
- Name Your app
適当なアプリ名を入力して、[us-each-1 blue]を選択します。
- What’s your fronted tech?
JSを選択します。
- What’s your backend tech?
Larvalを選択します。
[Create My App]ボタンをクリックするとアプリが作成されます。
アプリの詳細ページの右側に、app_id、key、secretが表示されます。
これらの値を、Laravelプロジェクトに設定します。
Pusherをプロジェクトに設定
Laravalフレームワークには、PusherのPHPライブラリの呼び出しが予め含まれており(Illuminate/Broadcasting/BroadcastManager.php
あたりを参照)、config/broadcasting.php
を確認するとデフォルトでブロードキャストの設定にはPusherが選択されています。
そのため、PusherをLaravelプロジェクトに組み込むには、ライブラリの追加とサービスの画面で作成したPusherアプリケーションの情報を設定するだけです。
Pusherのライブラリは、Composerでインストールできます。
$ composer require pusher/pusher-php-server
You are running composer with xdebug enabled. This has a major impact on runtime performance. See https://getcomposer.org/xdebug
Using version ^2.3 for pusher/pusher-php-server
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
- Installing pusher/pusher-php-server (v2.3.0)
Downloading: 100%
Writing lock file
Generating autoload files
> php artisan clear-compiled
> php artisan optimize
Generating optimized class loader
Compiling common classes
Pusherのサービス画面で作成したアプリケーションを開きapp_id、key、secretをコピーして.envの最後に追加します。
# .env
PUSHER_KEY=YOUR_PUSHER_KEY
PUSHER_SECRET=YOUR_PUSHER_SECRET
PUSHER_APP_ID=YOUR_PUSHER_APP_ID
クライアント側の実装
最後に、ビューでPusher Javascriptライブラリの読み込み、Pusherからの通知の受信を実装していきます。
resources/views/index.blade.php
にライブラリの読み込みを追加します。
// resources/views/index.blade.php
...
<script src=”js/app.js”></script>
<script src=”https://js.pusher.com/3.0/pusher.min.js”></script>
<script>
var pusher = new Pusher(”{{ env('PUSHER_KEY') }}”);
</script>
<script src=”js/pusher.js”></script>
</body>
</html>
public/js/pushuer.js
を作成して、itemAction
チャンネルのリスナーを実装します。
// public/js/pusher.js
( 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);
以上で、実装は完了です。
ブラウザを2つ以上立ち上げてToDoの追加/削除を行うと、ブラウザの更新なしに反映されることが確認できます。
まとめ
以上のように、Laravelはイベント機構と通知の仕組みをPusherを利用することで無理なく実装することができます。
また、Laravelのブロードキャストイベントは、Pusherの他にRedisのPubSubを利用したブロードキャストも対応しています。
Regisとnode.jsのsocket.ioを組み合わせた例については、「Laravel 5.1- Broadcasting Events using Redis Driver, Socket.io」がよくまとまっていそうなので、こちらも試してみたいところです。