Laravel+Vueでリアルタイム・チャットをつくる(ダウンロード可)

さてさて、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.phpindex()にアクセスして取得したデータをmessagesに格納しているだけです。

そして、ページが表示されたらすぐに全メッセージを取得できるようにmounted()内でgetMessages()を実行します。

mounted() {

    this.getMessages();

}

これでページが表示されたら、自動的に全メッセージのデータが取得できるようになりました。

では、次は取得した全メッセージをVue.jsを使って表示していきましょう。

<div v-for="m in messages">

    <!-- 登録された日時 -->
    <span v-text="m.created_at"></span>:&nbsp;

    <!-- メッセージ内容 -->
    <span v-text="m.body"></span>

</div>

やっていることは、v-forを使って全メッセージをループさせ、必要なデータをv-textで取り出しているだけです。これを実行すると以下のようになります。

リアルタイムに更新する部分をつくる

さぁ、やっと本題にたどり着きました。
ここからLaravel Echoを使って、

  1. メッセージを登録する
  2. 自動的に全チャットページを更新する

という部分をつくっていきます。

流れとしては、

  1. LaravelからPusherへ変更があったことを通知
  2. 待機しているLaravel EchoがPusherを通じて変更を感知
  3. 全メッセージをもう一度取得してページ更新

となります。そのためまず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_DRIVERpusherへ変更します。

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を変更します。

まずこれまで使ってきたvueaxios/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.phpcreate()で起動します。

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.phpBroadcastServiceProviderを解除
  • .envの編集
  • app.jsのビルド
Laravelでリアルタイム・チャットを作るファイル一式