開発ブログ

株式会社Nextatのスタッフがお送りする技術コラムメインのブログ。

電話でのお問合わせ 075-744-6842 ([月]-[金] 10:00〜17:00)

  1. top >
  2. 開発ブログ >
  3. PHP >
  4. Laravel >
  5. Laravel EchoとPusherでリアルタイムチャットを作ってみた。

Laravel EchoとPusherでリアルタイムチャットを作ってみた。

こんにちは。
ニシザワです。

本日はLaravel EchoとPusherを使ってリアルタイムチャットが簡単にできたのでその方法を書きます。

Pusherの登録

Pusherは簡単に双方向通信ができるAPIサーバーのサービスです。
まずは、こちらの登録をします。
次にCreate new appよりアプリを作成してください。
スクリーンショット 2018-09-26 21.55.04.png
③は今回はなんでもいいですが、Vanilla Jsを選択
④はLaravelを選択
⑤は適当にコメントを入れていただいてCreate my appで作成してください。

Eventの作成

app/Events/以下にposted.phpを作成しEventを作成します。
<?php
namespace App\Events;

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

class Posted implements ShouldBroadcast
{
    use SerializesModels;

    /**
     * @var Post
     */
    public $post;

    /**
     * Posted constructor.
     * @param Post $post
     */
    public function __construct(Post $post)
    {
        $this->post = $post;
    }

    /**
     * @return Channel|Channel[]
     */
    public function broadcastOn()
    {
        return new Channel('post');
    }
}
new Channelのところはrouteで定義するchannel名を入れます。
今回は認証は入れませんが、入れたい場合はPrivateChannelを使います。

Routeの定義

route/channels.phpに以下を追加
<?php
Broadcast::channel('post', function (){
    return true;
});
コールバックのところは認証を入れる場合はここで制限します。
今回は認証など条件は必要ないので常にtrueを返しています。

投稿に必要なCreateとIndexを作成

表示と投稿用を実装します。

migration

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePostsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->increments('id');
            $table->text("text");
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
}

Route

route/web.php
<?php

Route::get("posts", 'PostController@index')->name('post.index');
Route::post("posts/create", 'PostController@create')->name('post.create');

Model

app/Models/Post.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

/**
 * Class Post
 * @package App\Models
 *
 * @property string $text
 */
class Post extends Model
{

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'text'
    ];
}

Controller

<?php
declare(strict_types=1);

namespace App\Http\Controllers;

use App\Events\Posted;
use App\Models\Post;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class PostController extends Controller
{

    /**
     * @return View
     */
    public function index() : View
    {
        $posts = Post::all();
        return view('channels.index',[
            "posts" => $posts
        ]);
    }

    /**
     * @param Request $request
     * @return JsonResponse
     */
    public function create(Request $request) : JsonResponse
    {
        $post = new Post($request->all());
        $post->save();
        event(new Posted($post));

        return response()->json(['message' => '投稿しました。']);
    }
}

View

resources/views/channels/index.blade.php
@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">
                        <ul id="board">
                            @foreach($posts as $post)
                                <li>{{ $post->text }}</li>
                            @endforeach
                        </ul>
                    </div>

                    <div class="card-body">
                        <input type="text" id="text">
                        <input type="submit" value="送信" id="submit">
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection
LaravelのAuthで作られるテンプレートを代用してます。

Pusherの設定

composer require pusher/pusher-php-server "~3.0"
composerで必要なパッケージをインストールします。
npm install --save laravel-echo pusher-js
Laravel EchoとpusherJsもインストールします。
.env

BROADCAST_DRIVER=pusher
PUSHER_APP_ID=your id
PUSHER_APP_KEY=your key
PUSHER_APP_SECRET=your secret
PUSHER_APP_CLUSTER=your claster
次に環境変数の設定です。
pusherのダッシュボード>App Keysにあるのでそちらから写します。
config/app.phpで
//App\Providers\BroadcastServiceProvider::class,
コメントアウトされているBroadcast用のProviderを外します。

Laravel Echoでリスナーを作成

resources/assets/js/bootstrap.js

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
});
上記のコードを追加します。
resources/assets/js/app.js

$(document).ready(function() {
    $.ajaxSetup({
        headers: {
            'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
        }
    });
    $("#submit").click(function () {
        const url = "/posts/create";
        $.ajax({
            url: url,
            data: {
                text: $("#text").val()
            },
            method: "POST"
        });
        return false;
    });
    window.Echo.channel('post')
        .listen('Posted', (e) => {
            $("#board").append('<li>' + e.post.text + '</li>');
        });
});
app.jsの最後に投稿したときの投稿処理とpusherから受けとるリスナー処理を書きます。
channel('post')のpostはrouteで定義したchannel名になります。
listen('Posted'のPostedはEventで定義したクラス名が入ります。
名前空間などが違う場合はEventで定義したクラスに別名をつけることも可能です。

ざっと説明しましたが、これで完了です!
ブラウザを2つ開いて、送信を押してみてください。
スクリーンショット 2018-09-26 22.09.25.png

片方で送信すると片方のブラウザにリアルタイムで表示されると思います。
とても簡単にできますのでぜひ活用してみてください。
TOPに戻る