九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、Laravelの機能は時間を追うごとにどんどん増えていっているわけですが、さすが世界中の優秀なプログラマーたちが新しい便利な機能をプルリクエストしているだけあってそのスピードは目を見張るものがあります。
ただそうなってくると、「今は必要ないけどいつかは試してみたい機能」が増えてしまって、まさに「積ん読(つんどく)」状態になってしまったりします。
実は私自身もいくつか試してみたい機能があるんですが、今日はそんな中から「Laravel Echo」を取り上げて記事を書くことにしました。
Larael Echoは「ブロードキャスティング」と呼ばれるリアルタイムでブラウザにイベント通知(受信)ができる機能で、今回はこれを利用してリアルタイムチャットをつくってみます。
ぜひ、みなさんもやってみてくださいね。
目次
やりたいこと(ゴール)
例えばブラウザでタブを2つ開いて、片方のページから「ハロー!」と送信すると、もう片方のページが自動的に更新される簡易的なチャットシステム。
複雑になってしまうと良くないので、今回ログイン機能は使わず誰でもチャットに参加できるようなものを作ります。
なお、開発環境はLaravel 5.7です!
メッセージ・テーブルをつくる
まず送信されたテキストを保存しておくテーブルをマイグレーションで作ります。(どうせモデルも必要になるのでモデル + マイグレーションを一気につくります)
php artisan make:model Message -m
コマンドを実行したら、作成された****_**_**_******_create_messages_table.php
を開いてup()
の中身を以下のように変更します。
public function up() { Schema::create('messages', function (Blueprint $table) { $table->increments('id'); $table->text('body'); // メッセージ本文 $table->timestamps(); }); }
そして、マイグレーションを実行してテーブルを作成しましょう。
php artisan migrate
マイグレーションを実行したらテーブルはこうなっています。
また、データ追加の際にエラーが発生するので、Message
モデルに$guarded
を追加しておいてください。
class Message extends Model { protected $guarded = ['id']; }
そして、チャット文がいくつかあると後で開発がしやすいので、Seederを使ってmessages
テーブルにテストデータを追加しておきましょう。
php artisan make:seed MessagesTableSeeder
database/seeds/MessagesTableSeeder.php
を開いて以下のようなテスト用のテキストをいくつか追加します。
public function run() { for($i = 1 ; $i <= 10 ; $i++) { \App\Message::create([ 'body' => $i .'番目のテキスト' ]); } }
では、database/seeds/DatabaseSeeder.php
に登録してSeederを実行しましょう。
public function run() { $this->call(UsersTableSeeder::class); $this->call(MessagesTableSeeder::class); }
php artisan migrate:fresh --seed
Seederの実行が完了すると、テーブルは以下のようになります。
チャットページを作る
では次に、ユーザーが実際に目にするチャットページを作っていきましょう。
Routeを追加する
以下のRouteをweb.php
に追加して/chat
にアクセスできるようにします。
Route::get('chat', 'ChatController@index');
Controllerを追加する
まだChatController
はつくっていないので以下のコマンドで作成。
php artisan make:controller ChatController
そして、作成されたapp/Http/Controllers/ChatController.php
を開いてindex()
の中でchat
という名前のビューを設定します。
public function index() { return view('chat'); // フォームページのビュー }
ビューを追加する
もちろんビューもまだ作っていないので、resources/views/chat.blade.php
というファイルを作成し、必要なタグをここに追加します。
<html> <body> <div id="chat"> <textarea v-model="message"></textarea> <br> <button type="button">送信</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script> <script> new Vue({ el: '#chat', data: { message: '' } }); </script> </body> </html>
※ cdnでVue.js
、そしてAjax通信用にaxios
を読み込んでいます。
※ また、テキストエリアにはVueのmessage
という変数をバインディングして、リアルタイムでこの情報が反映されるようにしておきます。
見た目はこんな風になりました。
Ajax部分をつくる
では、次にAjaxを通して入力されたメッセージをmessages
テーブルに保存する部分を作っていきます。
送信ボタンにクリックイベントを追加して、
<button type="button" @click="send()">送信</button>
methods
に登録します。
new Vue({ el: '#chat', data: { message: '' }, methods: { send() { const url = '/ajax/chat'; const params = { message: this.message }; axios.post(url, params) .then((response) => { // 成功したらメッセージをクリア this.message = ''; }); } } });
send()
でやっていることは、テキストエリアに入力された内容をAjaxで送信。そして、登録が成功したらテキストエリア内をクリアするようにしています。
では、次にLaravel側でこのAjaxに関連する部分を作っていきましょう。
まず、以下のコマンドでチャットのAjax専用のコントローラーを作成します。
php artisan make:Controller Ajax\\ChatController
app/Http/Controllers/Ajax/ChatController.php
にファイルが作成されるので、このファイルを開いて以下2つのメソッドを追加します。
public function index() {// 新着順にメッセージ一覧を取得 return \App\Message::orderBy('id', 'desc')->get(); } public function create(Request $request) { // メッセージを登録 \App\Message::create([ 'body' => $request->message ]); }
※注: 本来はcreate()
メソッドにバリデーションを追加しておくべきですが、今回はテストなので省略しています。
また、この2つのメソッドにアクセスできるようweb.php
にRouteも追加しておきましょう。
Route::get('ajax/chat', 'Ajax\ChatController@index'); // メッセージ一覧を取得 Route::post('ajax/chat', 'Ajax\ChatController@create'); // チャット登録
では、Ajax部分の準備は完了です。
フォームに「ハロー!」と入力して送信し、データを追加してみましょう。
送信するとmessages
テーブルはこうなります。
うまくデータ登録ができました。
全メッセージを表示する
では、次は登録されたメッセージ一覧を表示する部分を作っていきましょう。
まず、Vueに変数として全メッセージを格納するmessages
を追加します。
data: { message: '', messages: [] },
そして、Ajaxを通して全メッセージを取得するgetMessages()
も作成しましょう。
methods: { getMessages() { const url = '/ajax/chat'; axios.get(url) .then((response) => { this.messages = response.data; }); }, // 以下略
やっていることは、app/Http/Controllers/Ajax/ChatController.php
のindex()
にアクセスして取得したデータをmessages
に格納しているだけです。
そして、ページが表示されたらすぐに全メッセージを取得できるようにmounted()
内でgetMessages()
を実行します。
mounted() { this.getMessages(); }
これでページが表示されたら、自動的に全メッセージのデータが取得できるようになりました。
では、次は取得した全メッセージをVue.jsを使って表示していきましょう。
<div v-for="m in messages"> <!-- 登録された日時 --> <span v-text="m.created_at"></span>: <!-- メッセージ内容 --> <span v-text="m.body"></span> </div>
やっていることは、v-for
を使って全メッセージをループさせ、必要なデータをv-text
で取り出しているだけです。これを実行すると以下のようになります。
リアルタイムに更新する部分をつくる
さぁ、やっと本題にたどり着きました。
ここからLaravel Echoを使って、
- メッセージを登録する
- 自動的に全チャットページを更新する
という部分をつくっていきます。
流れとしては、
- LaravelからPusherへ変更があったことを通知
- 待機しているLaravel EchoがPusherを通じて変更を感知
- 全メッセージをもう一度取得してページ更新
となります。そのためまずPusherに登録しておきましょう。
Pusherに登録する
「Sandbox plan」と呼ばれるプランは、無料プランでありながら以下のように相当ヘビーな使い方もできる仕様になっています。(2018/10/10現在)
- 最大100接続
- チャンネル数は無制限
- 1日に200,000回メッセージまで
- ちょっとしたサポートあり
- SSLで暗号化
では登録してみましょう。
まず登録ページにアクセスし、
- あなたのメールアドレス
- パスワード
- 組織名
を入力して「CREATE A FREE ACCOUNT」をクリックします。
すると、以下のように本登録を促すメッセージがでます。
本登録のURLがメールに届いているので、「Confirm my account」をクリックしてPusherへの会員登録は完了です。
ちなみに以下が登録が完了したところです。
では、ログインページから登録したメールアドレスとパスワードを使ってログインしましょう。
すると初回ログイン時は親切にもすぐ利用するサイトを登録できるようにしてくれているので、
- Name your app ・・・ アプリ(サイト)名
- Select a cluster ・・・ おそらくシンガポールになっています。そのままでOK。
を入力して「Create my app」ボタンをクリックします。
これで、サイトの登録が完了しました。
Laravel側でブロードキャストの準備をする
では、次にLaravelでリアルタイム通知が利用できるよう設定します。
Pusherの「App Keys」をクリックして接続情報を確認してください。
そして、これらの情報を.env
に書き込んでおきましょう。
PUSHER_APP_ID=****** PUSHER_APP_KEY=******************** PUSHER_APP_SECRET=******************** PUSHER_APP_CLUSTER=***
そして、ブロードキャストにはPusherを使うので、同じく.env
内のBROADCAST_DRIVER
はpusher
へ変更します。
BROADCAST_DRIVER=pusher
また、config/app.php
内にあるBroadcastServiceProvider
がデフォルトではコメント化されているので、これを解除して利用できるようにします。
/* * Application Service Providers... */ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class,
解除するとこうなります。
App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, App\Providers\BroadcastServiceProvider::class, // ←ここを解除しました App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class,
ただし、このままの状態でページにアクセスすると、
という具合にPusher用のパッケージがないと言われてしまうので、以下のコマンドで専用パッケージをインストールしておきます。
composer require pusher/pusher-php-server "~3.0"
では、次にPusherとのつなぎ役をしてくれるLaravel Echo(JavaScriptのライブラリ)を以下のコマンドでインストールします。(もしまだnpmのパッケージを全くインストールしていない場合は、npm install
で初期パッケージをインストールしておいてください)
npm install --save laravel-echo pusher-js
インストールが完了したら、resources/js/bootstrap.js
を開いて、Laravel Echoに関するコードのコメント化を解除します。
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 });
※上のコードは初期状態で全てコメント化されているので解除します。
では、以下のコマンドでビルドしてapp.js
を更新しましょう。
npm run dev
ビルドが完了したら、再びresources/views/chat.blade.php
を変更します。
まずこれまで使ってきたvue
とaxios
は/js/app.js
で全て代替ができるのでそっくり入れ替えます。
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
↓↓↓入れ替える
<script src="/js/app.js"></script>
ではこの状態で一度チャットページにアクセスして通信状態を見てみましょう。
websocket
の通信が現れました。
通知イベントを作成する
あと少しとなりました。
では、次にmessages
テーブルに新規登録されたらPusherにその内容を送信する部分をつくっていきましょう。
まずは、以下のコマンドで専用イベントMessageCreated
を作成します。
php artisan make:event MessageCreated
そして、作成されたapp/Events/MessageCreated.php
を開いて、ShouldBroadcast
インターフェイスを実装。そして、さらにコンストラクタでメンバ変数$message
に登録されたデータを格納します(Pusherは通知するには、public
である必要があります)
また、broadcastOn()
の中で、チャンネルを通常のnew Channel
にし、名をchat
にします。
<?php namespace App\Events; use App\Message; 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 MessageCreated implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; public $message; /** * Create a new event instance. * * @return void */ public function __construct(Message $message) { $this->message = $message; } /** * Get the channels the event should broadcast on. * * @return \Illuminate\Broadcasting\Channel|array */ public function broadcastOn() { return new Channel('chat'); } }
※ 太字が変更した部分です。
では、メッセージ登録されたらMessageCreated
が自動的に実行されるようにしましょう。以下のようにapp/Http/Controllers/Ajax/ChatController.php
のcreate()
で起動します。
public function create(Request $request) { $message = \App\Message::create([ 'body' => $request->message ]); event(new MessageCreated($message)); }
※ モデルの$dispatchesEvents
に設定してもいいのですが、マイグレーションを実行した際にもこのイベントが呼ばれてしまう関係からこの形にしました。
ではこの状態で一度登録データがPusherに送信されるか確認してみましょう。
まずは、Pusherの「Debug Console」をクリックして表示しておいてください。
そして、チャットページに今度は「アロハー!」と入力して送信してみましょう。
すると、Pusherのコンソールに以下の内容が表示されました。
送信成功です!
では、最後にPusherからの通知を待機するLaravel EchoのコードをVueのmounted()
内に追加して作業は終了です。
mounted() { this.getMessages(); Echo.channel('chat') .listen('MessageCreated', (e) => { this.getMessages(); // 全メッセージを再読込 }); }
では、大変長らくお待たせしました!
緊張のチャットテストを実行してみましょう。
チャットページを2つ開いて、片方から「ボンジュール!」と送信してみます。
さぁ、どうなるでしょうか??
うまく更新されました!
画像ではわかりにくいですが、左側の画面は何も触っていないですが、自動的に更新されて「ボンジュール!」という文字が表示されるようになりました。もちろん逆側からも同じです。
以上でLaravelでリアルタイム・チャットを作る方法でした。
お疲れ様でした!
ファイルをダウンロード
今回の説明で作成したファイル一式を以下からダウンロードできます。
※ ただし、以下の項目はご自身で行っていただく必要があります。
config/app.php
のBroadcastServiceProvider
を解除.env
の編集app.js
のビルド
Pusherへデータ送信ができない場合(追記: 2019.06.03)
訪問者様からご連絡をいただいて知ったことなのですが、どうやら環境(おそらくPHPのモジュールの影響かと思われます)が違うとPusher側にデータ送信ができないケースがあるようです。
もしこの状況になった場合は、以下の手順でencrypted
を解除してみてください。
まず、/config/broadcasting.php
のencrypted
をfalse
へ変更します。
'connections' => [ 'pusher' => [ 'driver' => 'pusher', 'key' => env('PUSHER_APP_KEY'), 'secret' => env('PUSHER_APP_SECRET'), 'app_id' => env('PUSHER_APP_ID'), 'options' => [ 'cluster' => env('PUSHER_APP_CLUSTER'), 'encrypted' => false, ], ], // 省略
そして、/resouces/js/bootstrap.js
も同様にencrypted
をfalse
へ変更します。
window.Echo = new Echo({ broadcaster: 'pusher', key: process.env.MIX_PUSHER_APP_KEY, cluster: process.env.MIX_PUSHER_APP_CLUSTER, encrypted: false });
さらに、このファイルはビルドしないと使えませんので以下のコマンドを実行しましょう。
npm run dev # もしくは npm run production
以上です!