LaravelのBroadcastingとは

LaravelのBroadcastingの説明を行う前にイメージ図を使ってLaravel Broadcastingを理解しておきましよう。

下記の図のように真ん中にあるLaravelサーバはテレビ局の電波塔のように地域全体に電波が飛ばすように情報を送信します。Laravelサーバから送信された情報は送信されてくるのを待ち受けているブラウザによって受け取れます。サーバが特定のブラウザを指定して情報を送信しているわけではなく不特定多数のブラウザに向けて送信しているのでBroadcastingと呼ばれます。テレビとは異なりブラウザはいつも情報が送信されてくるのを待っているわけでなくブラウザからサーバに対して処理の依頼を行うこともできます。ブラウザから依頼を受けた処理がサーバ上で終了したあとに送信される更新情報にもBroadcastingが使われることもあります。

broadcastの意味にはテレビはラジオで番組を送るという意味とたくさんの人に情報を広めるという意味があります。
fukidashi
Laravelサーバからメッセージ送信
Laravelサーバからメッセージ送信

各家庭で好きな番組を視聴する際にチャンネルを合わせて受信するようにLaravelサーバにアクセスしている各ブラウザもチャネルを設定してLaravelからの情報を受信します。WebSocketという技術を使ってブラウザをリロードすることなくリアルタイムで受け取った情報をブラウザ上に描写することができます。

LaravelのBroadcastingにはPusherとRedisという2つ方法が準備されています。今回の説明ではPusherを用いた方法でBroadcastingの動作確認を行います。

Pusherを利用した場合のBroadcastingを図にすると以下のようになります。Laravelからブラウザに直接データが送信されるわけではなく、LaravelのEventを利用してPusherにデータを送信し、Pusherを経由してブラウザに情報が渡されます。渡されたデータはJavaScriptを使ってブラウザをリロードすることなくブラウザに反映することができます。

LaravelとPusherとブラウザの関係
LaravelとPusherとブラウザの関係
Pusherは外部のサービスなので、サービスへのアカウント登録が必要になります。無料のプランが準備されているので動作確認程度だとお金はかかりません。また登録時にはクレジット情報も聞かれません。
fukidashi

一度にすべての動作を確認するより3つのパートに順を追って説明したほうが理解しやすいため以下の3つのパートにわけて説明を行なっていきます。1と2でLaravelサーバから送られたデータがブラウザに届くまでの流れを理解することができます。3では送られたデータをVue.jsを使ってブラウザをリロードすることなしにブラウザに反映させる方法を確認します。

  1. LaravelからEventを使い、Pusherにデータを送信、Pusherが受信
  2. Pusherから送られたデータをブラウザで受信
  3. Laravel→Pusher→ブラウザにきたデータをVue.jsを使い描写

LaravelからPusherへのデータ送信

Laravelのバージョンは6.0を利用しています。

Laravelのインストール

まず最初にLaravelのインストールを行います。


$ composer create-project --prefer-dist laravel/laravel laravel_echo

JavaScriptライブラリを利用するので、npmのインストールを行います。


$ cd laravel_echo
$ npm install

Broadcastingの設定

Broadcastingの機能はデフォルトでは使えないようになっているので使えるように関連するファイルを更新していきます。

Broadcasting機能に関する設定は各種ファイルの中に記述されていますが、コメントアウトされています。
fukidashi

Laravelのインストールが完了したら、configディレクトリにあるapp.phpファイルを開いてコメントされているBroadcastServiceProviderのコメントを外します。


    ・
    ・
App\Providers\AuthServiceProvider::class,
App\Providers\BroadcastServiceProvider::class, //ここがコメントされている
App\Providers\EventServiceProvider::class,
    ・
    ・

次にconfigディレクトリにあるbroadcastingの設定ファイルbroadcasting.phpを開いて、設定されているドライバの確認を行います。デフォルトではnullになってるので、.envファイルでどのドライバが使われているか確認します。.envファイルはLaravelインストールディレクトリに保存されています。


'default' => env('BROADCAST_DRIVER', 'null'),
ドライバによってredisを使うかpusherを使うのか設定を行うことができます。デバッグ、動作確認用にlogというドライバも準備されています。nullは何もしない設定です。
fukidashi

.envファイルを見るとlogに設定されています。Broadcastingが動作するか確認するために最初はlog設定のままで進めます。


BROADCAST_DRIVER=log

イベントの作成

Broadcatingで送信するデータはイベント経由で行います。

イベントの知識がなくても動作確認は行えますが、Laravelのイベントがどのような機能だったか確認したい場合は下記を参考にしてください。

イベントの作成は、php arisan make:eventコマンドで行います。タスクが追加されたという情報を送信したいので、イベント名はTaskAddedとします。


$ php artisan make:event TaskAdded
Event created successfully.

app¥EventsディレクトリにTaskAdded.phpファイルが作成されます。Broadcastingの設定を行うのでTaskAdded.phpファイルを開きます。Broadcastingを行うためにTaskAddedクラスにShouldBroadcastインターファイスを継承させる必要があります。


use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class TaskAdded implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

ShouldBroadcastインターファイスの中にはbroadcastOnメソッドのみ記述されています。

インターフェイスは設計図のようなものなので、ShouldBroadcastには実際のコードは記述されておらず、継承したクラスが実装しなければならないメソッドが記述されています。
fukidashi

namespace Illuminate\Contracts\Broadcasting;

interface ShouldBroadcast
{
    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|\Illuminate\Broadcasting\Channel[]
     */
    public function broadcastOn();
}

作成したTaskAddedにもbroadcastOn()メソッドが実装されています。PrivateChannelを使っていますが、Channelクラスに変更します。チャネル名をtask-added-channelに変更します。


public function broadcastOn()
{
    return new PrivateChannel('channel-name');
}
Private Channelではユーザの認証が必要なので、ユーザ認証の必要がないChannelを使っています。本文書ではPrivate Channelについて後半で設定を行います。
fukidashi

変更後はbroadcastOnメソッドは下記のようになります。


public function broadcastOn()
{
    return new Channel('task-added-channel');
}

web.phpを開いて、/tasksルーティングを追加し、作成したTaskAddedイベントを下記のようにeventヘルパー関数で発行させます。ブラウザで”/tasks”にアクセスするとTaskAddedイベントが発行されます。


use App\Events\TaskAdded;

Route::get('/tasks', function () {
    event(new TaskAdded);
});

Broadcastingの初期動作確認

php artisan serveコマンドで開発サーバを起動します。


$ php artisan serve
Laravel development server started: <http://127.0.0.1:8000>

ブラウザを使ってサーバにアクセスします。アクセスしたあとにstorage/logsの下にあるログファイルを開いてください。.envファイルのbroadcastのドライバをlogに設定していたので、ログファイルに下記の情報が書き込まれます。


[2019-09-08 01:03:26] local.INFO: Broadcasting [App\Events\TaskAdded] on channels [task-added-channel] with payload:
{
    "socket": null
}

イベント名と設定したチャネル名を確認することができればここまで設定したBroadcastingの設定は問題なく行われています。

Pusherのアカウント登録

Pusherを利用するためにはアカウント登録を行う必要があります。Pusher.comにアクセスして、Sign up for freeボタンをクリックします。

Pusherトップページ
Pusherトップページ

アカウント情報の入力画面が表示されるので、Github、Googleアカウントもしくはメールアドレスを入力します。

アカウント情報登録画面
アカウント情報登録画面

メールアドレスを入力した場合は下記のアカウント認証画面が表示されます。メールがPusherから届いているのでメールを確認してアカウント認証を行います。

メール認証画面
メール認証画面

アカウント認証が完了するとアプリケーションの名前をつける画面が表示されるので、任意の名前をつけてください。select a clusterではap3(Asia Pacific(Tokyo))に設定します。設定が完了したら、Create my appボタンをクリックしてください。ここでは名前をtestにしました。

choose your tech stackは選択しなくても大丈夫です。
fukidashi
プロジェク名入力画面
アプリケーション名入力画面

アプリケーションの作成が完了するとクライアント側、サーバ側で使用している言語やフレームワークを選択できる画面が表示されます。今回はクライアント側でLaravel Echo、サーバ側でLaravelを使用するのでタブでそれぞれを選択してください。

ログイン後の画面
ログイン後の画面

変更するとクライアント側でインストールしなければならないJavaScriptライブラリと受信用のコード、サーバ側ではインストールしなければならないPHPパッケージでPusherの接続に必要なAPI KEYの情報が表示されます。

クライアントとサーバ環境を選択
クライアントとサーバ環境を選択

Pusherに接続するためのAPP KeysはApp Keysタグからも確認することができます。

APP KEYSの確認
APP KEYSの確認

Pusher設定(Laravel側)

Pusherを使用するためのAPP Keysの情報が入手できたので、設定を行なっていきます。まず、Pusherを利用するために必要なパッケージをcomposerを使ってインストールします。


 $ composer require pusher/pusher-php-server

pusherパッケージのインストールが完了したら、.envファイルを開いてPusher接続用の情報を設定します。


PUSHER_APP_ID=856176
PUSHER_APP_KEY=0a3d15b7980b0719ae17
PUSHER_APP_SECRET=fb9e693d112c6c64267f
PUSHER_APP_CLUSTER=ap3

.envファイルのBROADCAST_DRIVERをlogからpusherに変更します。


BROADCAST_DRIVER=pusher

Pusher側でLaravelから送られてくる情報を確認するためにDebug Consoleを開きます。

Pusher Debug Console
Pusher Debug Console

設定はすべて完了したので、ブラウザからhttp://localhost:8000/tasksにアクセスしてPusherに情報が届くのか確認します。

.envファイルを更新して設定が反映されない場合は。php artisan config:cacheを実行してください。
fukidashi
Laravelからのメッセージ
Laravelからのメッセージ

PusherのDebug Console画面にチャネル名とイベント名が表示されたら、Pusherへのメッセージ送信が正常に動作しています。

Pusherに追加データを送信する

先ほどはPusherにはデータが届きましたが、チャンネル名とイベント名以外の情報がなかったので、Pusherに送信する情報に追加データをつけてみましょう。

変数$taskを作成し配列でidとnameを持たせます。


Route::get('/tasks', function () {
	
    $task = ['id' => 1, 'name' => 'メールの確認']; 

    event(new TaskAdded($task));
    
});

TaskAddedイベントファイルで$taskをPusherに送る設定を行います。


class TaskAdded implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $task;

    public function __construct($task)
    {
        $this->task = $task;
    }

    public function broadcastOn()
    {
        return new Channel('task-added-channel',$this->task);
    }
    
}

再度、ブラウザで/taskにアクセスしましょう。PusherのDebug Consoleに追加したtaskの情報が表示されたら、情報の追加も正常に行われています。

$taskの情報が表示
$taskの情報が表示

Pusherからのデータをブラウザで受信

LaravelからPusherへデータが送信できることを確認しました。次は、Pusherが受け取ったデータをブラウザ側で表示されるための設定を行います。Laravel Echoを利用して受信を行うので、2つのJavaScriptライブラリのインストールを行います。

JavaScriptライブラリのインストール

npmコマンドでlaravel-echoとpusher-jsライブラリをインストールします。


$ npm install --save laravel-echo pusher-js

LaravelではJavaScriptファイルを更新するとコンパイルが必要となります。ファイルの更新を監視できるようにnpm run watchコマンドを実行します。


$ npm run watch
特に後半のvue.jsを使ったコードではJavaScriptの更新を頻繁に行います。npm run watchのコンパイルは忘れずに実行しておいてください。
fukidashi

JavaScriptファイルの更新

インストールしたlarave-echoとpusher-jsライブラリに関する記述はでデフォルトではコメントされているので、コメントを外す必要があります。

resource¥js¥bootstrap.jsを開いてコメントされているPusherに関する設定のコメントを外します。


import Echo from 'laravel-echo';

window.Pusher = require('pusher-js');

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    encrypted: true
});

上記のMIX_PUSHER_APP_KEYとMIX_PUSHER_APP_CLUSTERは.envファイルの中で設定が行われています。Pusherからのデータを受け取るためには、クライアントであるブラウザ側ではAPP_KEYとAPP_CLUSTERの2つが必要であることがわかります。


MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

Bladeファイルの更新

クライアントであるブラウザ側でデータを受信するということはLaravelにアクセスするブラウザが表示するBladeファイルでデータを受け取るためのコードを記述する必要があります。

受信の動作確認を行うためにwelcome.blade.phpファイルを使用します。

welocome.blade.phpファイルを開いてapp.jsファイルをscriptタグで読み込みます。


    <script src="/js/app.js"></script>
</body>

また、Laravel EchoではCSRFトークンを利用するのでmetaタグを追加する必要があります。


<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">

<title>Laravel</title>

ここまでの設定でPusherからのデータの受信は行えませんが、Pusherへの接続設定が完了したwelcome.blade.phpを開くとPusherへ接続が行われることを確認することができます。

ブラウザからのConnection
ブラウザからのConnection

LaravelからデータをPusherに送信する設定が完了し、クライアントであるブラウザがPusherに接続することが確認できました。最後に必要なのは、ブラウザがPusherからデータを受け取ることなのでその設定を行います。

JavaScriptファイルでの受信設定

次にPusherからのメッセージが受け取れるようにするためにbootstrap.jsで追加設定を行う必要があります。channelメソッドにはイベントTaskAddedで設定したチャネル名、listenメソッドには、イベント名を指定します。Pusherからのデータはdataに入ってくるので、console.logコマンドでコンソールにメッセージでPusherから送られてくるデータを表示させます。


window.Echo.channel('task-added-channel')
		   .listen('TaskAdded',function(data){
			  console.log('received a message');
			  console.log(data);
			});

まず、ブラウザを開いて”/”ルートにアクセスを行います。

上記のチャネルとイベントを設定する前は、”/”にアクセスしても2行の情報しか表示されませんでしたが、新たにSubscribed等チャネルに関する追加情報が表示されています。

Pusherで接続確認
Pusherで接続確認

次に別のブラウザを開いて/taskにアクセスを行います。/taskにアクセスしたのでpusherに向けてLaravelから情報が送信されます。

Laravelからの情報が表示
Laravelからの情報が表示

ブラウザのコンソールを開いてみるとConsoleにメッセージとPusherから受け取ったtaskの情報が表示されていることが確認できます。データを受け取る際はブラウザのリロード等は必要ありません。

ブラウザで受信確認
ブラウザで受信確認

ここまでの流れを通して、LaravelからPusherへのデータの送信、Pusherへのクライアントの接続(ブラウザ)とPusherからのデータの受け取りを確認することができました。

“/”ルートにアクセスするブラウザ1つだけで動作確認を行いましたが、同時に複数のブラウザで”/”を表示している時に別のブラウザで”/tasks”にアクセスすると”/”にアクセスしているすべてのブラウザでPusherからの情報を受け取ることができます。

[付録]Laravel Echoライブラリを使用しない場合

npmコマンドでLaravel Echoライブラリのインストールを行いましたが、Laravel Echoを使用しない場合でもPusherからのデータの受け取りを確認しておきましょう。

PusherのGet startedタグにあるJavaScriptのコードを参考にしています。
fukidashi

bootstrap.jsで設定したいたlaravel-echoとpusherに関する設定をコメントします。


// import Echo from 'laravel-echo';

// window.Pusher = require('pusher-js');

// window.Echo = new Echo({
//     broadcaster: 'pusher',
//     key: process.env.MIX_PUSHER_APP_KEY,
//     cluster: process.env.MIX_PUSHER_APP_CLUSTER,
//     encrypted: true
// });

// window.Echo.channel('task-added-channel')
// 		   .listen('TaskAdded',function(data){
// 			  console.log('received a message');
// 			  console.log(data);
// 			});

次にwelcome.blade.phpにpusherのライブラリをcdnで読み込み、Pusherからのデータ受け取りのコードを記述します。設定はチャネルとイベント、PusherのAPI Key情報を入れるだけです。


<script src="https://js.pusher.com/5.0/pusher.min.js"></script>
<script>

// Enable pusher logging - don't include this in production
Pusher.logToConsole = true;

var pusher = new Pusher('0a3d15b7980b0719ae17', {
  cluster: 'ap3',
  forceTLS: true
});

var channel = pusher.subscribe('task-added-channel');
channel.bind('TaskAdded', function(data) {
       console.log('received a message');
       console.log(data);
});
</script>
Pusher.logToConsoleはロギングモードでコンソールにPusherのログを自動で出力してくれます。
fukidashi

1つのブラウザで”/”ルートにアクセスし、もう一つのブラウザで”/tasks”にアクセスすると下記のようにPusherからのデータを確認することができます。

JavaScriptのみを使用した場合
JavaScriptのみを使用した場合

Pusherのロギングモードにしているので、Pusherとの接続やWebsocketの情報も表示されます。

Pusherとの接続の詳細
Pusherとの接続の詳細

Vue.jsを使ってデータを表示

vue.jsを使って受け取ったデータをブラウザに表示される方法を確認します。Todoリストを表示するプログラムを作成し、新たにTodoを追加すると、その一覧を表示しているすべてのブラウザ上でTodoの追加が行われます。

Vueライブラリの設定

Laravelでvue.jsに利用できるようにvue.jsに関する設定を追加します。

vue.jsを追加するためにlaravel/uiパッケージをインストールする必要があります。


$ composer require laravel/ui
Laravel6.0になり、laravel/uiパッケージが追加されました。vueやreactのフロンとサイドのアプリケーションや認証機能の追加で必要になります。
fukidashi

php artisanコマンドを利用して、vue.jsだけではなく認証機能(本文書では使用しない)も合わせて追加します。


$ php artisan ui vue --auth
Vue scaffolding installed successfully.
Please run "npm install && npm run dev" to compile your fresh scaffolding.
Authentication scaffolding generated successfully.
ここでの認証機能の追加はユーザを登録するための登録画面やログイン画面の追加です。
fukidashi

php artisanコマンド実行後に下記のnpmコマンドを実行します。JavaScriptのライブラリのインストールを行い、JavaScriptファイルのコンパイルを行なっています。


$ npm install && npm run dev

次にTodoリストのデータを保存するためにデータベースを作成します。

データベースの接続

データを保存するためにはデータベースを作成する必要があります。簡易的に作成できるsqliteデータベースを使用します。

Laravelのインストールディレクトリの下にあるdatabaseディレクトリにdatabase.sqliteファイルを作成します。


$ touch database/database.sqlite

.envファイルを開いてDB_CONNECTIONをデフォルトのmysqlからsqliteに変更し、DB_CONNECTION以外のDB設定のパラメータを削除します。


DB_CONNECTION=sqlite

これでデータベースの設定は完了なので、php artisan migrateコマンドを実行し、認証に使用するusersテーブルが作成できるか確認します。


$ php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.02 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (0 seconds)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (0 seconds)

上記のようにテーブルの作成に成功したら、データベースへの接続設定も問題なく行われています。

tasksテーブルの作成

todoリストを保存するためにtodosテーブルを使用します。php arisanコマンを使ってmigrationファイルとTodo.phpファイルを作成します。


$ php artisan make:model Todo -m
Model created successfully.
Created Migration: 2019_09_08_112219_create_todos_tabl

テーブルの構造はname列を追加だけのシンプルなものです。


public function up()
{
    Schema::create('todos', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->string('name');
        $table->timestamps();
    });
}

php artisan migrateコマンドでtodosテーブルを作成します。


$ php artisan migrate
Migrating: 2019_09_08_112219_create_todos_table
Migrated:  2019_09_08_112219_create_todos_table (0.02 seconds)

Todo.phpファイルを開いてname列の設定を行います。


namespace App;

use Illuminate\Database\Eloquent\Model;

class Todo extends Model
{
    protected $fillable = ['name'];
}

todoの各種設定

web.phpにtodosに関するルーティングを追加します。ブラウザから”/todos”にアクセスするとTodoリストの一覧が表示されます。


Route::get('/todos', function(){
	return view('todos.index');
});

resources¥viewsの下にtodosディレクトリを作成し、index.blade.phpファイルを作成します。


<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">

        <title>Todos</title>
    </head>
    <body>
        <div id="app">

        <todos-list></todos-list>

        </div>

        <script src="/js/app.js"></script>
    </body>
</html>

<todos-list></todos-list>のvue.jsのコンポーネントでtodosをリスト化を行う部分です。後ほど作成します。JavaScriptの/js/app.js、csrf_tokenは忘れずに追加してください。

vueコンポーネントの作成

Pusherから受け取ったデータを描写する部分なのでここからが重要になります。

コンポーネントtodos-listのための設定を行うため、resources¥js¥app.jsファイルを開いてください。

下記の行がデフォルトで設定されていますが、ExapleComponentは使用しないので、TodoListCompomentに変更します。


Vue.component('example-component', require('./components/ExampleComponent.vue').default);

[変更後]


Vue.component('todos-list', require('./components/TodoListComponent.vue').default);

resources¥js¥componentsの下にTodoListComponent.vueを作成します。

axiosを利用して、todosテーブルからリストを取得して、表示するコードを作成します。


<template>
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <h1>Todoリスト</h1>
                <ul>
                <li v-for="todo in todos">{{ todo['name'] }}</li>
                </ul>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        data(){
            return {
                todos : []
            }
        },
        mounted(){
            axios.get('/api/todos').then(response => (this.todos = response.data));                                      
        },
    }
</script>

routes¥api.phpファイルを開いて、axiosで/api/todosにアクセスがあった場合にtodoリストを戻すルーティングを追加します。


Route::get('/todos',function(){

	return \App\Todo::all();
	
});

ブラウザで/todosにアクセスした時に下記の画面が表示されます。

Todoリストの表示画面
Todoリストの表示画面

まだtodoテーブルにはデータが登録されていないのでリストには何も表示されません。

input要素の追加

Todoリストにデータを追加できるようにinput要素を追加します。v-modelでnewTodoプロパティを設定し、blurイベントを設定し、addTodoメソッドを設定します。

blurイベントはinput要素からカーソルが外れたらイベントが発生(addTodoメソッド実行)しinput要素に入力した値が登録されます。
fukidashi

<input type="text" v-model="newTodo" @blur="addTodo">

JavaScript側の追加を行います。


export default {
    data(){
        return {
            todos : [],
            newTodo : ''
        }
    },
    mounted(){
        axios.get('/api/todos').then(response => (this.todos = response.data));
            
    },
    methods:{
        addTodo(){
            axios.post('/api/todos',{
                name : this.newTodo
            })
            .then(response => this.todos.push(response.data));         

        this.newTodo = '';

        }
    }
}

axiosのPOSTリクエストで送ったデータをtodosテーブルに追加できるようにroutes¥api.phpに新たにルーティングを追加します。


Route::post('/todos',function(){

	$todo = \App\Todo::create(request()->all());

	return $todo;
	
});

ブラウザで/todosにアクセスしてinput要素に追加したTodoがリストに表示されていくことを確認してください。

input要素に入力してカーソルを外すとリスト追加
input要素に入力してカーソルを外すとリスト追加

ここまでの確認

todosにアクセスして、input要素にTodoを入力するとvue.jsの設定により、入力したブラウザではブラウザをリロードすることなくTodoリストに項目が追加されていきます。しかし、別のブラウザで/todosにアクセスしてもアクセスした時点でのTodoリストの一覧は取得できますが、その後別のブラウザでTodoを追加してもそのブラウザには追加は反映されません。ブラウザをリロードして初めて他のブラウザで追加された値がTodoリストに反映されます。

イベントの作成

Todoリストが作成されたという情報をPusherに送るためにイベントの作成を行います。


$ php artisan make:event TodoAdded
Event created successfully.

app¥eventsの下にTodoAdded.phpファイルが作成されます。チャネル名はto-added-channelという名前で$todoを一緒にPusherに送信します。


namespace App\Events;

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

class TodoAdded implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $todo;

    public function __construct($todo)
    {
        $this->todo = $todo;
    }

    public function broadcastOn()
    {
        return new Channel('to-added-channel',$this->todo);
    }
}
ShouldBroadcastインターフェイスを忘れずに継承してください。
fukidashi

イベントはTodoリストを追加した時に発行されるので、routes¥api.phpにイベントのコードを追加します。



Route::post('/todos',function(){

	$todo = \App\Todo::create(request()->all());

	event((new todoAdded($todo));

	return $todo;
	
});

上記のイベントを追加したので、Todoリストを追加したら、Pusherに追加したTodoの情報が送信されることになります。”/todos”にアクセスしてTodoを追加してみましょう。

Pusherで追加したTodo確認
Pusherで追加したTodo確認

Pusherに上記のように追加の情報が表示されたらLaravelからPusherへの送信は正常に動作しています。

Pusherから情報を受け取る

Pusherからデータを受け取り、Todoリストに追加したTodoをリアルタイムで表示されるようにTodoListCompomentコンポーネントを更新します。

window.Echoの部分を追加しており、channelにはTodoAddedで設定したチャネル、listeneにはTodAddedのイベント名を設定します。Pusherから受け取ったデータはtodosのリストに追加しています。


export default {
    data(){
        return {
            todos : [],
            newTodo : ''
        }
    },
    mounted(){
        axios.get('/api/todos').then(response => (this.todos = response.data));

        window.Echo.channel('todo-added-channel')
                    .listen('TodoAdded',response => {
                        this.todos.push(response.todo);
                    });            
    },
    methods:{
        addTodo(){
            axios.post('/api/todos',{
                name : this.newTodo
            })
            .then(response => this.todos.push(response.data));         

        this.newTodo = '';

        }
    }
}

/todosにアクセスして、Todoリストを追加するとaddTodoメソッドで追加した分とPusherから受け取った分の2行が一度に追加されます。

一度に2行追加された画面
一度に2行追加された画面

入力を行なったブラウザには先ほど説明した理由で2行の情報が表示されます。Pusherからの更新が入力を行なったブラウザ上で行われないように設定する必要があります。routes¥api.phpを開いて、dontBroadcastToCurrentUserメソッドをevent関数に下記のように追加します。


event((new TodoAdded($todo))->dontBroadcastToCurrentUser());

設定を行なったあと、Todoに追加すると1行しか追加が行われなくなります。また、別のブラウザを開いている場合は、Todoを追加すると別のブラウザでもTodoへ追加した内容がリストに追加されることが確認できます。

メソッドの追加により1行のみ追加
メソッドの追加により1行のみ追加

別のブラウザの画面です。

別のブラウザで追加を確認
別のブラウザで追加を確認

特にvue.jsと組み合わせたブラウザの表示では説明が長くなりましたが、Pusherから受け取ったデータをリアルタイムにリロードなしで追加されることが確認できました。

Private Channelを設定する

ここまで設定したChannelはPublic Channelでだれでもこのチャネルから情報を取得することができました。ここではユーザ認証を利用することでチャネルへのアクセスを制限を行うPrivate Channelの動作確認を行います。

テーブルへの列追加

todosテーブルに新たにuser_idの列を追加します。user_idを追加し設定を行うことでtodoリストを作成したユーザのみPusherからの情報を受け取れるようになります。

todosテーブルのマイグレーションファイルを開いてuser_idを追加します。


public function up()
{
    Schema::create('todos', function (Blueprint $table) {
        $table->bigIncrements('id');
        $table->string('user_id');
        $table->string('name');
        $table->timestamps();
    });
}

php artisan migration:refreshコマンドを使用してテーブルを再作成します。


$ php artisan migrate:refresh
テーブルに必要なデータが残っている時はmigrate:refreshは絶対に行わないでください。
fukidashi

user_idにデータが挿入できるようにTodo.phpを開いて下記の$fillable変数にuser_idを追加してください。


protected $fillable = ['name','user_id'];

ユーザ登録

Private Channelではユーザ認証を利用するためシステムへのユーザ登録を行います。

ユーザ登録画面
ユーザ登録画面

テーブルの登録部分の変更

todosテーブルに新たにuser_idを追加したのでリスト追加のプログラムを変更する必要があります。

これまではデータ登録時にnameのみ必要でしたが今後はuser_idが必要になります。このuser_idはシステムにログインしている認証済みのユーザ情報です。


methods:{
    addTodo(){
        axios.post('/api/todos',{
            name : this.newTodo,
            user_id : //ここにuser_idが必要
        })
        .then(response => this.todos.push(response.data));         

    this.newTodo = '';

    }
}

views¥todos¥index.htmlファイルを開いてscriptタグの中で認証ユーザの情報を取得します。認証済みユーザの場合のみ情報が取得できる設定です。

<script> window.user = {!! auth()->user() !!}; </script>

上記でユーザ情報が取得できるので、addTodoメソッド内のuser_idは下記のように記述することができます。


        axios.post('/api/todos',{
            name : this.newTodo,
            user_id : user_id : window.user['id']
        })

Private Channelへの設定変更

これまではテーブルとユーザに関する設定変更でしたが、ここではChannelからPrivate Channelへの変更を行います。

TodoAddedイベントのTodoAdded.phpファイルを開いてChannelからPrivateChannelに変更します。さらにチャネル名にIDをつけます。


public function broadcastOn()
{
    return new PrivateChannel('todo-added-channel.'.$this->todo->user_id);
}

次にTodolistComponent.vueファイルを開きます。

windows.Echoのchannelメソッドをprivateメソッドに変更し、チャネル名にユーザのIDをつけます。これでTodoAddedで設定したチャンネル名と同じ形になります。


window.Echo.private('todo-added-channel.' + window.user['id'])
            .listen('TodoAdded',response => {
                this.todos.push(response.todo);
            }); 

最後にroutes¥channels.phpファイルを開いてチャネルの設定を行います。$user変数には、ログインしているユーザの情報が入り$idには、TodolistComponent.vueファイルで設定したwindow.user[‘id’]の値が入ることになります。下記の設定では認証済みのユーザのみtodo-added-channel.{userId}のチャネルを受け取ることができます。


Broadcast::channel('todo-added-channel.{userId}', function ($user, $id) {
    return (int) $user->id === (int) $id;
});
だれにチャネルから情報を受け取れるかを設定する重要な部分です。今回は一番単純はユーザIDのチェックのみ行なっています。
fukidashi

todoリストを追加した場合にTodoAddedイベントが発行されますが、チャネル名は、’todo-added-channel.’.$this->todo->user_idになっているので、todoリストを追加したユーザのみがPusherから送られてくるデータを受け取ることができます。

Pusher上での確認

PusherでのDebug Consoleを見ながら流れを確認していきましょう。

作成したユーザでログインを行い、/todosにアクセスをすると
Channel: private-todo-added-channel.1でSUBSCRIBEDされていることがわかります。

ユーザIDが付与されたチャネル名
ユーザIDが付与されたチャネル名

Todoリストへの追加を行います。

リストに追加する
リストに追加する

追加を行うとイベントが発生するのでPusherに情報が送信されます。送信されたチャネル名を見るとChannel:private-todo-added-channel.1になっていることがわかります。Subcribedした時と同じチャネル名を持っているのでこのユーザにはPusherからの情報を受け取ることができます。

ユーザIDが付与されたチャネルでのイベント
ユーザIDが付与されたチャネルでのイベント

もし、IDの異なるユーザでログインを行うと異なるチャネル名になります。Channel:private-todo-added-channel.2になっています。チャネル名が異なるので、Channel:private-todo-added-channel.1からの情報を受け取ることができません。

異なるユーザでログイン
異なるユーザでログイン

このようにPrivate Channelではチャネル名とユーザ認証を使ってPusherからのデータを受け取れるユーザを制限しています。

認証されていないユーザでのログイン

システムに登録されていないユーザでログインを行うと下記のuser情報が取得できないので値なしのエラーが発生します。

<script> window.user = {!! auth()->user() !!}; </script>

ゲストユーザでもアクセスできるシステムではauth()->check()などを使ってゲストユーザの場合は値なし等の設定を行う必要があります。

ここまで読み終えた方はPusherを使用したLaravel Broadcastingの基本が理解できたと思います。