イントロダクション

多くの近代的なアプリケーションでは、リアルタイムで、ライブ更新されるユーザインターフェイスを実装するために、WebSocketが使用されています。サーバ上で何かのデータが更新されると、通常メッセージがWebSocket接続を通じメッセージが送信され、クライアントにより処理されます。これはアプリケーションの変更を更新し続ける方法の代わりとして、より強固で効率的です。

こうしたタイプのアプリケーション構築を援助するため、LaravelはイベントをWebSocket接続を通じて簡単に「ブロードキャスト」できます。Laravelのイベントをブロードキャストすることで、サーバーサイドのコードとクライアントサイドのJavaScriptで同じ名前のイベントを共有できるようになります。

Tip!! ブロードキャストを開始する前に、Laravelのイベントとリスナに関するドキュメントをすべてしっかりと読んでください。

設定

イベントブロードキャストの設定オプションはすべて、config/broadcasting.php設定ファイルの中にあります。Laravelはドライバをいくつか準備しています。PusherRedis、それにローカルの開発とデバッグのためのlogドライバがあります。さらにブロードキャストを完全に無効にするための、nullドライバも用意しています。config/broadcasting.php設定ファイルに、各ドライバの設定例が含まれています。

ブロードキャストサービスプロバイダ

イベントをブロードキャストするには、事前にApp\Providers\BroadcastServiceProviderを登録する必要があります。インストールしたてのLaravelアプリケーションで、config/app.php設定ファイル中の、providers配列配列にある、このプロバイダーのコメントを外してください。このプロバーダーはブロードキャスト認証ルートとコールバックを登録します。

CSRFトークン

Laravel Echoは、現在のセッションのCSRFトークンにアクセスする必要があります。可能であれば、EchoはLaravel.csrfToken JavaScriptオブジェクトから、トークンを取得します。このオブジェクトは、make:auth Artisanコマンドを実行していれば、resources/views/layouts/app.blade.phpレイアウトで定義されています。このレイアウトを使用しない場合には、アプリケーションのhead HTMLエレメント中で、metaタグを定義する必要があります。

<meta name="csrf-token" content="{{ csrf_token() }}">

ドライバ要求

Pusher

イベントをPusherによりブロードキャストする場合、Composerパッケージマネージャを使い、Pusher PHP SDKをインストールする必要があります。

composer require pusher/pusher-php-server

次に、Pusherの認証情報をconfig/broadcasting.phpファイル中で設定する必要があります。Pusherの設定例は、このファイルに含まれアプリケーションIDを簡単に指定できます。config/broadcasting.phpファイルのpusher設定では、Pusherでサポートされているクラスタなど、追加のオプション(options)も設定可能です。

'options' => [
    'cluster' => 'eu',
    'encrypted' => true
],

PusherとLaravel Echoを使用する場合、Echoインスタンスをインスタンス化する時に、使用するブロードキャスタとして、pusherを指定する必要があります。

import Echo from "laravel-echo"

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: 'your-pusher-key'
});

Redis

Redisブロードキャスタを使用する場合は、Predisライブラリをインストールする必要があります。

composer require predis/predis

RedisブロードキャスタはRedisのpub/sub機能を使用し、メッセージをブロードキャストします。Redisからのメッセージを受け、WebSocketチャンネルへブロードキャストできるように、これをWebSocketとペアリングする必要があります。

Redisブロードキャスタがイベントを発行すると、そのイベントに指定されたチャンネル名へ発行され、イベント名、dataペイロード、イベントのソケットIDを生成したユーザ(該当する場合)を含む、ペイロードはJSONエンコードされた文字列になります。

Socket.IO

RedisブロードキャスタとSocket.IOサーバをペアリングする場合、アプリケーションのhead HTML要素で、Socket.IO JavaScriptクライアントライブラリをインクルードする必要があります。

<script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>

次に、socket.ioコネクタとhostを指定し、Echoをインスタンス化します。たとえば、アプリケーションとソケットサーバがapp.devドメイン上で動作している場合、Echoは次のようにインスタンス化します。

import Echo from "laravel-echo"

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: 'http://app.dev:6001'
});

最後に、Socket.IOのコンパチブルサーバを実行する必要があります。LaravelにはSocket.IOサーバの実装は含まれていません。しかし、tlaverdure/laravel-echo-server GitHubリポジトリで、コミュニティにより現在、Socket.IOサーバがメンテナンスされています。

キュー事前要件

イベントをブロードキャストし始める前に、キューリスナを設定し、実行する必要もあります。イベントのブロードキャストは、すべてキュージョブとして行われるため、アプリケーションのレスポンスタイムにはシリアスな影響はでません。

概論

Laravelのイベントブロードキャストは、サーバサイドのLaravelイベントから、WebSocketに対する駆動ベースのアプローチを使っている、あなたのクライアントサイドのJavaScriptアプリケーションへ、ブロードキャストできるようにします。現在、PusherとRedisドライバーが用意されています。Laravel Echo JavaScriptパッケージを使用したクライアントサイド上で、イベントは簡単に利用できます。

パブリック、もしくはプライベートに指定された「チャンネル」上で、イベントはブロードキャストされます。アプリケーションの訪問者は、認証も認可も必要ないパブリックチャンネルを購入します。プライベートチャンネルを購入するためには、認証され、そのチャンネルをリッスンできる認可が必要です。

サンプルアプリケーションの使用

イベントブロードキャストの各コンポーネントへ飛び込む前に、例としてeコマースショップを使い、ハイレベルな概念を把握しましょう。このドキュメント中の別のセクションで詳細を説明するため、PusherLaravel Echoの設定についての詳細は省きます。

このアプリケーションでは、ユーザに注文の発送状態を確認してもらうビューページがあるとしましょう。さらに、アプリケーションが発送状態を変更すると、ShippingStatusUpdatedイベントが発行されるとしましょう。

event(new ShippingStatusUpdated($update));

ShouldBroadcastインターフェイス

ユーザがある注文を閲覧している時に、ビューの状態を変更するために、ユーザがページを再読込しなくてはならないなんてしたくありません。代わりにアップデートがあることをアプリケーションへブロードキャストしたいわけです。そのため、ShouldBroadcastインターフェイスを実装した、ShippingStatusUpdatedイベントを作成する必要があります。このインターフェイスはイベントが発行されると、ブロードキャストすることをLaravelへ指示しています。

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class ShippingStatusUpdated implements ShouldBroadcast
{
    //
}

ShouldBroadcastインターフェイスはイベントで、broadcastOnメソッドを定義することを求めています。このメソッドはイベントをブロードキャストすべきチャンネルを返す責任を持っています。イベントクラスを生成すると、既にこのメソッドはからのスタブクラスに作成されていますので、詳細を埋めるだけになっています。オーダーの発注者だけに状態の変更を見てもらいたいので、そのオーダーに紐付いたプライベートチャンネルへ、イベントをブロードキャストしましょう。

/**
 * イベントをブロードキャストすべき、チャンネルの取得
 *
 * @return array
 */
public function broadcastOn()
{
    return new PrivateChannel('order.'.$this->update->order_id);
}

認証中チャンネル

プライベートチャンネルをリッスンするには、ユーザは認可されている必要があることを思い出してください。BroadcastServiceProviderbootメソッドで、チャンネルの認可ルールを定義してください。この例の場合、プライベートorder.1チャンネルをリッスンしようとするユーザは、実際にそのオーダーの発注者であることを確認しています。

Broadcast::channel('order.*', function ($user, $orderId) {
    return $user->id === Order::findOrNew($orderId)->user_id;
});

channelメソッドは引数を2つ取ります。チャンネルの名前と、ユーザにそのチャネルをリッスンする認可があるかどうかをtruefalseで返すコールバックです。

全認可コールバックは、最初の引数に現在認証中のユーザを受け取ります。その後の引数は、追加のワイルドカードパラメータです。この例の場合、チャンネル名のワイルドカードとして使用している*文字は、"ID"の部分を表しています。

イベントブロードキャストのリッスン

次に、皆さんのJavaScriptアプリケーションでイベントをリッスンします。このために、Laravel Echoが利用できます。最初に、プライベートチャンネルを購読するために、privateメソッドを使います。それから、ShippingStatusUpdatedイベントをリッスンするために、listenメソッドを使用します。デフォルトでは、イベントのpublicプロパティは、すべてブロードキャストイベントに含まれています。

Echo.private('order.' + orderId)
    .listen('ShippingStatusUpdated', (e) => {
        console.log(e.update);
    });

ブロードキャストイベントの定義

Laravelへイベントをブロードキャストすることを知らせるためには、そのイベントクラスでIlluminate\Contracts\Broadcasting\ShouldBroadcastインターフェイスを実装します。このインターフェイスは、フレームワークにより生成されたすべてのイベントクラスで、useされていますので、イベントへ簡単に追加できます。

ShouldBroadcastインターフェイスは、broadcastOnメソッド一つのみ実装を求めています。broadcastOnメソッドは、そのイベントをブロードキャストすべきチャンネルか、チャンネルの配列を返します。チャンネルはChannelPrivateChannelPresenceChannelのインスタンスです。Channelインスタンスはユーザが行動するパブリックチャンネルを表しています。一方、PrivateChannelPresenceChannelは、チャンネル認可が必要な、プライベートチャンネルを表しています。

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class ServerCreated implements ShouldBroadcast
{
    use SerializesModels;

    public $user;

    /**
     * 新しいイベントインスタンスの生成
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * イベントをブロードキャストするチャンネルの取得
     *
     * @return Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('user.'.$this->user->id);
    }
}

これで、あと必要なのは、通常通りにイベントを発行するだけです。イベントを発行すると、キュージョブが指定済みのドライバを通して、自動的にそのイベントをブロードキャストします。

ブロードキャスト名

Laravelはデフォルトで、イベントのクラス名を使い、そのイベントをブロードキャストします。しかし、イベントにbroadcastAsメソッドを定義すると、ブロードキャスト名をカスタマイズできます。

/**
 * イベントブロードキャスト名
 *
 * @return string
 */
public function broadcastAs()
{
    return 'server.created';
}

ブロードキャストデータ

イベントがブロードキャストされると、イベントのペイロードとしてpublicプロパティーはすべて自動的にシリアライズされます。これによりJavaScriptアプリケーションより、publicデータにアクセスできます。ですから、たとえば、あるイベントにEloquentモデルを含む、publicの$userプロパティがあれば、そのイベントのブロードキャストペイロードは、次のようになります。

{
    "user": {
        "id": 1,
        "name": "Patrick Stewart"
        ...
    }
}

しかしながら、ブロードキャストペイロードをより上手くコントロールしたければ、そのイベントへbroadcastWithメソッドを追加してください。このメソッドから、イベントペイロードとしてブロードキャストしたいデータの配列を返してください。

/**
 * ブロードキャストするデータを取得
 *
 * @return array
 */
public function broadcastWith()
{
    return ['id' => $this->user->id];
}

ブロードキャストキュー

デフォルトでは各ブロードキャストイベントは、queue.php設定ファイルで指定されているデフォルトキュー接続の、デフォルトキューへ投入されます。イベントクラスのbroadcastQueueプロパティを定義することにより、使用するキューをカスタマイズできます。このプロパティには、ブロードキャスト時に使用したいキューの名前を指定してください。

/**
 * イベントを投入するキューの名前
 *
 * @var string
 */
public $broadcastQueue = 'your-queue-name';

認証中チャンネル

プライベートチャンネルでは、現在の認証ユーザが実際にそのチャンネルをリッスンできるか、認可する必要があります。これは、Laravelアプリケーションへチャンネル名を含めたHTTPリクエストを作成し、アプリケーションにそのユーザが、そのチャンネルをリッスンできるかを決めさせることで実現します。Laravel Echoを使用する場合、プライベートチャンネルへの購入許可HTTPリクエストは、自動的に作成されます。しかし、そうしたリクエストに対してレスポンスする、ルートを確実に定義する必要があります。

認証ルート定義

嬉しいことに、Laravelでは、チャンネル認可にクエストに対するレスポンスのルート定義も簡単です。Laravelアプリケーションに含まれているBroadcastServiceProviderで、Broadcast::routesメソッドが呼びだされているのが見つかります。このメソッドが認可リクエストを処理する、/broadcasting/authルートを登録しています。

Broadcast::routes();

Broadcast::routesメソッドは自動的に、そのルートをwebミドルウェアグループの中に設置しますが、割り付ける属性をカスタマイズしたければ、メソッドへルート属性の配列を渡すことができます。

Broadcast::routes($attributes);

認証コールバック定義

次に、チャンネル認可を実際に行うロジックを定義する必要があります。認可ルートと同様に、BroadcastServiceProviderbootメソッドの中で行います。このメソッドの中で、Broadcast::channelメソッドを使い、チャンネル認可コールバックを登録します。

Broadcast::channel('order.*', function ($user, $orderId) {
    return $user->id === Order::findOrNew($orderId)->user_id;
});

channelメソッドは2つの引数を取ります。チャンネル名と、そのチャンネルをリッスンする認可が、ユーザにあるかどうかを示す、truefalseをリターンするコールバックです。

認可コールバックはすべて、現在の認証ユーザを最初の引数として受け取ります。それ以降の引数は、追加のワイルドカードパラメータです。この例の場合、チャンネル名の"ID"の部分を示す*文字を使用しています。

ブロードキャストイベント

イベントを定義し、ShouldBroadcastインターフェイスを実装したら、あとはevent関数を使い、イベントを発行するだけです。イベントディスパッチャは、そのイベントがShouldBroadcastインターフェイスにより印付けられていることに注目しており、ブロードキャストするためにイベントをキューへ投入します。

event(new ShippingStatusUpdated($update));

認証中ユーザの回避

イベントブロードキャストを使用するアプリケーションを構築しているとき、event関数をbroadcast関数へ置き換えることもできます。event関数と同様に、broadcast関数もイベントをサーバサイドリスナへディスパッチします。

broadcast(new ShippingStatusUpdated($update));

しかし、broadcast関数には、ブロードキャストの受取人から現在のユーザを除外できる、toOthersメソッドが用意されています。

broadcast(new ShippingStatusUpdated($update))->toOthers();

toOthersメソッドをいつ使うのかをよく理解してもらうため、タスク名を入力してもらうことで、新しいタスクをユーザが作成できる、タスクリストアプリケーションを想像してください。タスクを作成するためにアプリケーションは、タスクの生成をブロードキャストし、新しいタスクのJSON表現を返す、/taskエンドポイントへリクエストを作成するでしょう。JavaScriptアプリケーションがそのエンドポイントからレスポンスを受け取る時、その新しいタスクをタスクリストへ直接挿入するでしょう。次のようにです。

this.$http.post('/task', task)
    .then((response) => {
        this.tasks.push(response.data);
    });

しかし、タスク生成のブロードキャストも行うことを思い出してください。もし、JavaScriptアプリケーションがタスクをリストへ追加するため、このイベントをリッスンしていれば、二重にタスクがリストへ追加されるでしょう。一つはエンドポイントから、もう一つはブロードキャストからです。

これを解決するために、toOthersメソッドを使い、ブロードキャスタへ現在のユーザには、イベントをブロードキャストしないように指示できます。

設定

Laravel Echoインスタンスを初期化する時、接続へソケットIDをアサインします。VueとVueリソースを使用していれば、X-Socket-IDヘッダとして、送信する全リクエストへ自動的に付加されます。そのため、toOthersメソッドを呼び出す場合、LaravelはヘッダからソケットIDを取り除き、そのソケットIDの全接続にはブロードキャストしないように、ブロードキャスタに対し指示します。

VueとVueリソースを使用しない場合、JavaScriptアプリケーションでX-Socket-IDヘッダーを送信するように、設定する必要があります。ソケットIDはEcho.socketIdメソッドにより取得できます。

var socketId = Echo.socketId();

ブロードキャストの受け取り

Laravel Echoのインストール

Laravel EchoはJavaScriptライブラリで、チャンネルの購読とLaravelによるイベントブロードキャストのリッスンを苦労なしに実現してくれます。EchoはNPMパッケージマネージャにより、インストールします。以降の例で、Pusherブロードキャストを使用する予定のため、pusher-jsパッケージもインストールしています。

npm install --save laravel-echo pusher-js

Echoがインストールできたら、アプリケーションのJavaScriptで、真新しいEchoインスタンスを作成する準備が整いました。これを行うには、Laravelフレームワークに含まれている、resources/assets/js/bootstrap.jsファイルの最後が、良いでしょう。

import Echo from "laravel-echo"

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: 'your-pusher-key'
});

pusherコネクタを使うEchoインスタンスを作成するときには、clusterと同時に接続の暗号化を行うかどうかを指定することもできます。

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: 'your-pusher-key',
    cluster: 'eu',
    encrypted: true
});

イベントのリッスン

インストールが済み、Echoをインスタンス化したら、イベントブロードキャストをリスニングする準備が整いました。最初に、channelメソッドを使い、チャンネルインスタンスを取得し、それからlistenメソッドで特定のイベントをリッスンしてください。

Echo.channel('orders')
    .listen('OrderShipped', (e) => {
        console.log(e.order.name);
    });

プライベートチャンネルのイベントをリッスンしたい場合は、privateメソッドを代わりに使用してください。一つのチャンネルに対し、複数のイベントをリッスンする場合は、listenメソッドをチェーンして呼び出してください。

Echo.private('orders')
    .listen(...)
    .listen(...)
    .listen(...);

チャンネルの離脱

チャンネルを離脱するには、Echoインスタンスのleaveメソッドを呼び出してください。

Echo.leave('orders');

名前空間

上の例で、イベントクラスの完全な名前空間を指定していないことに、皆さん気がついたでしょう。その理由は、EchoはイベントがApp\Events名前空間へ設置されると仮定しているからです。しかし、ルートの名前空間を設定変更している場合は、Echoのインスタンス化時に、namespace設定オプションを渡してください。

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: 'your-pusher-key',
    namespace: 'App.Other.Namespace'
});

もしくは、Echoを使用し購入する時点で、イベントクラスへ.を使い、プリフィックスを付けてください。

Echo.channel('orders')
    .listen('.Namespace.Event.Class', (e) => {
        //
    });

プレゼンスチャンネル

プレゼンスチャンネルは、誰がチャンネルを購入しているかの情報を取得できる機能を提供しつつ、安全なプライベートチャンネルを構築します。これにより、他のユーザが同じページを閲覧していることを知らせるような、パワフルでコラボレート可能な機能を持つアプリケーションを簡単に構築できます。

プレゼンスチャンネルの許可

全プレゼンスチャンネルは、プライベートチャンネルでもあります。そのため、ユーザはアクセスする許可が必要です。プレゼンスチャンネルの認可コールバックを定義する場合、ユーザがチャンネルへ参加する許可があるならば、trueをリターンしないでください。代わりに、ユーザ情報の配列を返してください。

認可コールバックから返されるデータは、JavaScriptアプリケーションのプレゼンスチャンネルイベントリスナで利用できるようになります。ユーザがプレゼンスチャンネルへ参加する許可がない場合は、falsenullを返してください。

Broadcast::channel('chat.*', function ($user, $roomId) {
    if ($user->canJoinRoom($roomId)) {
        return ['id' => $user->id, 'name' => $user->name];
    }
});

プレゼンスチャンネルへの参加

プレゼンスチャンネルへ参加するには、Echoのjoinメソッドを使用します。joinメソッドは、既に説明したlistenメソッドに付け加え、herejoiningleavingイベントを購入できるようになっている、PresenceChannel実装を返します。

Echo.join('chat.' + roomId)
    .here((users) => {
        //
    })
    .joining((user) => {
        console.log(user.name);
    })
    .leaving((user) => {
        console.log(user.name);
    });

hereコールバックはチャンネル参加に成功すると、すぐに実行されます。そして、このチャンネルを現在購入している、他の全ユーザ情報を含む配列を返します。joiningメソッドは、チャンネルに新しいユーザが参加した時に実行されます。一方のleavingメソッドは、ユーザがチャンネルから離脱した時に実行されます。

プレゼンスチャンネルへのブロードキャスト

プレゼンスチャンネルはパブリックやプライベートチャンネルと同じように、イベントを受け取ります。チャットルームを例にしましょう。その部屋のプレゼンスチャンネルへのNewMessageイベントがブロードキャストされるのを受け取りたいとします。そのために、イベントのbroadcastOnメソッドで、PresenceChannelのインスタンスを返します。

/**
 * イベントをブロードキャストするチャンネルを取得
 *
 * @return Channel|array
 */
public function broadcastOn()
{
    return new PresenceChannel('room.'.$this->message->room_id);
}

パブリックやプライベートイベントと同様に、プレゼンスチャンネルイベントはbroadcast関数を使用し、ブロードキャストされます。他のイベントと同様に、ブロードキャストから受けるイベントから、現在のユーザを除くために、toOthersメソッドも利用できます。

broadcast(new NewMessage($message));

broadcast(new NewMessage($message))->toOthers();

Echoのlistenメソッドにより、参加イベントをリッスンできます。

Echo.join('chat.' + roomId)
    .here(...)
    .joining(...)
    .leaving(...)
    .listen('NewMessage', (e) => {
        //
    });

通知

イベントブロードキャストと通知をペアリングすることで、JavaScriptアプリケーションはページを再読み込みする必要なく、新しい通知を受け取ることができます。最初に、ブロードキャスト通知チャンネルの使用のドキュメントをよく読んでください。

ブロードキャストチャンネルを使用する通知の設定を済ませたら、Echoのnotificationメソッドを使用し、ブロードキャストイベントをリッスンできます。チャンネル名は、通知を受けるエンティティのクラス名と一致している必要があることを覚えておいてください。

Echo.private('App.User.' + userId)
    .notification((notification) => {
        console.log(notification.type);
    });

上記の例の場合、App\Userインスタンへ送られる通知はすべて、「ブロードバンド」チャンネルを通じ、コールバックにより受け取られます。App.User.*チャンネルのチャンネル認可コールバックは、Laravelフレームワークに用意されている、デフォルトのBroadcastServiceProviderに含まれています。