👏

【随時更新】Laravel6を使ってリアルタイムチャットを作る

2022/04/22に公開

参考にしたサイト
https://reffect.co.jp/laravel/laravel-broadcasting-understandig
https://readouble.com/laravel/6.x/ja/broadcasting.html
↓だからブラウザ更新しなくてリアルタイムで通知が来るのか〜と納得できたqiita記事です。
https://qiita.com/att55/items/da663f6e713c3bd073e8
ありがとうございます😀

laravelのバージョンは6系

完成系
gifなんで動作が遅く見えますが、もっと動作は早いです。
リロードしなくても相手のブラウザにメッセージが更新されていることがわかります。

動作のイメージはこんな感じ

0. pusherの登録

pusherを使うのに必要な環境をcomposerでインストール
"~4.0"をつけないとlaravel6ではうまく動かなかったです

composer require pusher/pusher-php-server "~4.0"

1. configディレクトリのapp.phpのBroadcastServiceProviderのコメントを外す

app.php
App\Providers\BroadcastServiceProvider::class,

2. .envファイルのBROADCAST_DRIVERの値をpusherとする

.env
BROADCAST_DRIVER=pusher

また.envファイルでapiキーを登録します。
pusherのGetting StartedのStep2でServerをLaravelと選択すると以下のようなキーを取得することができるので.envファイルに設定します。

PUSHER_APP_ID=英数字列が入ってます
PUSHER_APP_KEY=英数字列が入ってます
PUSHER_APP_SECRET=英数字列が入ってます

3. イベントの作成

ShouldBroadcastインターフェースを継承して、broadcastOnでreturn new Channel(‘チャンネル名’)とする。
イベント名をMessageAddedに、チャンネル名をmessage-added-channelにしました。
この時点でイベントを発火させると、pusherの方にデータを送ることができます。

MessageAdded.php
<?php

namespace App\Events;

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

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

    public $message;
    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct($message)
    {
        $this->message = $message;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('message-added-channel',[$this->message]);
    }
}

4. laravel-echoとpusher-jsライブラリをインストール

ブラウザでpusherから情報を受け取るためのライブラリーをインストールします。

npm install --save laravel-echo pusher-js

resource\js\bootstrap.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
});

5. 今回使用するbladeを作成

chatroom.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="message_area">

        </div>
        <div>
           <textarea id = "message" rows="5" cols="100"></textarea>
           <button id="submit">送信</button>
        </div>

        <script src="/js/app.js"></script>
        <script src="{{asset('js/chat.js')}}"></script>
    </body>
</html>

6. コントローラー

ChatroomController.php
<?php

namespace App\Http\Controllers;

use App\Events\MessageAdded;
use Illuminate\Http\Request;
use App\Message;
use PHPUnit\Util\Test;
use Illuminate\Support\Facades\Auth;

class ChatroomController extends Controller
{
    //チャット画面を表示
    public function index()
    {
        return view('chatroom');
    }

    //新しいメッセージが来たとき
    public function newmessage(Request $request)
    {
        $message = new Message();
        $message->message = $request->message;
        $message->user_id = Auth::id();
        $message->save();

        //イベント発動
        //新しいメッセージをpusherに
        event(new MessageAdded([$message,Auth::user()]));
    }

    //最初にアクセスした時、全メッセージを返す
    public function allmessage()
    {
        return Message::with('user')->get();
    }
}

7. 非同期通信のためのJavaScriptを書く(XmlHttpRequestを使用)

ajax通信する時、いつもjQueryに頼っていたので今回は以下の記事を参考にしました。
https://www.sejuku.net/blog/30245

chat.js
window.Echo.channel('message-added-channel')
.listen('MessageAdded',function(data){
   console.log('received a message');
   console.log(data);
   let newmessage = data.message[0].message;
   let name = data.message[1].name;
   let message = document.querySelector('#message_area');
   message.insertAdjacentHTML('beforeend',name+" "+newmessage +" "+data.message[0].created_at+"<br>");
 });

 //一度だけ実行される処理
 var xhr = new XMLHttpRequest();
 xhr.open('GET', '/allmessage');
 xhr.send();

 //最初にアクセスした時全てのメッセージを取得
 xhr.onreadystatechange = function() {
    if(xhr.readyState === 4 && xhr.status === 200) {
       const json = xhr.responseText;
       const obj = JSON.parse(json);
       console.log(obj);
       const message = document.querySelector('#message_area');
       for(let i = 0; i < obj.length; i++){
       message.insertAdjacentHTML('beforeend' , obj[i].user.name+" "+obj[i].message +" "+obj[i].created_at+"<br>");
       }
    }
}

 const submitbutton = document.querySelector('#submit');

 //送信ボタンをクリックした時
 submitbutton.addEventListener('click',()=>{
     const message = document.querySelector('#message');
     console.log(message.value);

     const xhr = new XMLHttpRequest();
     let token = document.getElementsByName('csrf-token').item(0).content;

     xhr.open('POST','/newmessage')
     xhr.setRequestHeader('X-CSRF-Token', token);
     xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
     console.log(message.value);
     xhr.send("message="+message.value);
     message.value = '';
 });

8. ルーティング

web.php
<?php

use App\Events\MessageAdded;
use Symfony\Component\EventDispatcher\Event;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
});

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');

// Route::get('/test', function () {
//     event(new MessageAdded('message!!!'));
// });

//ajax通信のルーティング
Route::post('/newmessage', 'ChatroomController@newmessage');
Route::get('/allmessage','ChatroomController@allmessage');

//チャットルームを表示
Route::get('/chatroom', 'ChatroomController@index')->middleware('auth');

Discussion