[Laravel8.46.0] laravel-echoとpusher-jsでchat機能を実装する

config/app.php
L BroadcastServiceProviderのコメントアウトを外します。

App\Providers\BroadcastServiceProvider::class,

クライアント側で必要なパッケージをインストール
$ npm install –save laravel-echo pusher-js

resources/js/bootstrap.js
L コメントアウトを外します

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,
    forceTLS: true
});

.env

BROADCAST_DRIVER=pusher
// 省略
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

$ php artisan make:model Message -m

migrationfile
L rollbackしてもう一度作り直します。

    public function up()
    {
        Schema::create('messages', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->integer('user_id');
            $table->text('message');
            $table->timestamps();
        });
    }

Model
Message.php

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

    public function user(){

    	return $this->belongsTo('App\Models\User');
    }

User.php

    public function messages(){
        return $this->hasMany('App\Models\Message');
    }

php artisan make:controller ChatsController

route

Route::get('/post', [ChatsController::class, 'index']);
Route::get('/messages', [ChatsController::class, 'fetchMessages']);
Route::post('/messages', [ChatsController::class, 'sendMessage']);

ChatsController.php

use App\Models\Message;
use Illuminate\Support\Facades\Auth;
use App\Events\MessageSent;

class ChatsController extends Controller
{
    //
    public function __construct(){
    	$this->middleware('auth');
    }

    public function index(){
    	return view('chat.post');
    }

    public function fetchMessages(){
    	return Message::with('user')->get();
    }

    public function sendMessage(Request $request){
    	$user = Auth::user();

    	$message = $user->messages()->create([
    		'message' => $request->input('message')
    	]);

    	event(new MessageSent($user, $message));

    	return ['status' => 'Message Sent!'];
    }
}

$ php artisan make:event MessageSent
MessageSent.php

use App\Models\User;
use App\Models\Message;

class MessageSent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
    public $user,
    public $message;

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

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('testApp');
    }
}

routes/channels.php
L プライベートチャンネルをリッスンする場合は、channels.phpで許可する

Broadcast::channel('testApp', function($user){
	return Auth::check();
});

post.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
	<link href="{{ mix('css/app.css')}}" rel="stylesheet" type="text/css">
	<meta name="csrf-token" content="{{ csrf_token() }}">
</head>
<body>
	<div id="app">
		<example-component></example-component>
	</div>

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

$ composer require laravel/ui
$ php artisan ui vue
$ npm install && npm run dev

/resources/js/components/ExampleComponent.vue

<template>
    <div>
        <ul>
            <li v-for="(message, key) in messages" :key="key">
                <strong>{{ message.user.name }}</strong>
                {{ message.message }}
            </li>
        </ul>
        <input v-model="text" />
        <button @click="postMessage" :disabled="!textExists">送信</button>
    </div>
</template>

<script>
    export default {
        data(){
            return {
                text: "",
                messages: []
            };
        },
        computed: {
            textExists(){
                return this.text.length > 0;
            }
        },
        created() {
            this.fetchMessages();
            Echo.private("testApp").listen("MessageSent", e=>{
                this.messages.push({
                    message: e.message.message,
                    user: e.user
                });
            });
        },
        methods: {
            fetchMessages(){
                axios.get("/messages").then(response =>{
                    this.messages = response.data;
                });
            },
            postMessage(message){
                axios.post("/messages", {message: this.text}).then(response => {
                    this.text = "";
                });
            }
        }
    }
</script>

あれ? 何やこれ。。。componentが上手く表示されんな。。。