laravel11 + reverb によるリアルタイムチャット

(2025-01-24)

laravel11 + reverb によるリアルタイムチャットをします。

ネット上に素晴らしいサイトがありました。

「Web屋さんの覚書」

この方がいなければ私はいつまでも迷路でもがいていたことでしょう。

でもこのサイトはトップの方にはないので随分と苦労しました。他のサイトはほとんど役に立ちません。

さすがの chatGPT もこの件に関しては全然だめで、延々と同じ過ちを繰り返し最後には chatGPT そのものが反応しなくなりました。 さんざんタダでこき使ってきたので AI といえどもちょっとすねたのかもしれません。

出来上がりのイメージ

環境

linux mint 22
Laravel Framework 11.39.1
PHP 8.3.6 (cli)

プロジェクト作成

参考サイトのほとんどコピペですが、私は docker は嫌いなので使いません。

composer create-project laravel/laravel:^11.0 larachat
cd larachat
php artisan serve

http://127.0.0.1:8000/で確認。

一旦、ctl + c でストップ。

その他の設定

npm 設定

npm install
npm install -D sass

reverb をインストール

php artisan install:broadcasting

message テーブル作成のためのマイグレーションテーブル作成

php artisan make:migration create_message_table

編集

database/2025_01_24_040322_create_message_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('messages', function (Blueprint $table) {
$table->id();
$table->string('message',255); // メッセージ格納用項目
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('messages');
}
};

マイグレーション

php artisan migrate

データ挿入のための seeder 作成。

php artisan make:seeder MessagesTableSeeder

編集

database/seeders/MessagesTableSeeder.php
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
class MessagesTableSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
DB::table('messages')->insert([
'message' => 'hello',
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
]);
}
}

シーディング。

php artisan db:seed --class=MessagesTableSeeder

モデル作成

php artisan make:model Message

chat bladeの新規作成

chat.blade.php を新規作成します。

resources/views/chat.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">
<title>簡易チャット</title>
@vite(['resources/js/app.js','resources/scss/chat.scss'])
</head>
<body>
<div>
<input type="text" id="message" name="message" placeholder="メッセージを書く">
<button id="send-button">送信</button>
</div>
<ul id="message-list">
@foreach ($messages as $message)
<li class="message">{{ $message->message }}</li>
@endforeach
</ul>
</body>
</html>

scss 設定

mkdir resources/scss

resources/scss/chat.scss を作成。

resources/scss/chat.scss
#message {
width: 500px;
padding: 10px;
margin: 10px 0;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 1em;
}
#send-button {
padding: 10px;
margin: 10px 0;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 1em;
background-color: #f0f0f0;
cursor: pointer;
}
#send-button:hover {
background-color: #e0e0e0;
}
#message-list {
list-style-type: none;
padding: 0;
margin: 0;
}
.message {
font-size: 1em;
color: #333;
list-style-type: none;
padding: 2px 10px;
}

vite.config.js の編集

vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/chat.css', 'resources/js/app.js'],
refresh: true,
}),
],
});

チャットコントローラ作成

php artisan make:controller ChatController

編集

app/Http/Controllers/ChatController.php
<?php
namespace App\Http\Controllers;
use App\Models\Message;
use Illuminate\Http\Request;
class ChatController extends Controller
{
//
public function index()
{
// 最後の20件を取得
$messages = Message::orderBy('created_at', 'desc')->take(20)->get();
$response = [
'messages' => $messages
];
return view('chat', $response);
}
}

ルーティング編集

routes/web.php
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ChatController;
Route::get('/', [ChatController::class, 'index']);

サーバー起動

別々のウィンドウで。

cd ~/larachat
npm run dev

cd ~/larachat
php artisan serve

で起動して、http://127.0.0.1:8000/で確認。

イベントの作成

php artisan make:event ChatEvent

編集

app/Event/ChatEvent.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;
use App\Models\Message;
class ChatEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
//この値をChat.jsで取得する
public $message;
/**
* Create a new event instance.
*/
public function __construct($message = null)
{
$this->message = $message;
$messages = new Message();
$messages->message = $message;
$messages->save();
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new Channel('channel-chat'),
];
}
}

chat.js の作成

resources/js/chat.js
/**
* Send a message to the server
*/
document.getElementById('send-button').addEventListener('click', () => {
const message = document.getElementById('message').value;
if (message) {
axios.post('/', { message: message }).then(() => {
document.getElementById('message').value = '';
});
}
});
/**
* Listen for events on the channel-chat channel
*/
Echo.channel('channel-chat').listen('ChatEvent', (e) => {
const newMessage = document.createElement('li');
newMessage.classList.add('message');
newMessage.textContent = e.message;
const ul = document.getElementById('message-list');
ul.prepend(newMessage);
});

app.js の修正

resources/js/app.js
import './bootstrap';
// Import the chat.js file
import './chat';

ルーティング変更

routes/web.php
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ChatController;
use App\Events\ChatEvent;
# トップページルーティング
Route::get('/', [ChatController::class, 'index']);
# ポスト用ルーティング
Route::post('/', function (Request $request) {
ChatEvent::dispatch($request->message);
});

確認

それぞれ別のウィンドウで、

cd ~/larachat
npm run dev

cd ~/larachat
php artisan reverb:start

cd ~/larachat
php artisan serve

cd ~/larachat
php artisan queue:work

で起動して、http://127.0.0.1:8000/で確認。

firefox と chrome など別のブラウザでアクセスして確認します。

いちいちいろいろ起動するのはとても面倒なのでシェルスクリプトを作成しました。

#!/bin/bash
# プロセス終了時に一括で停止する設定
trap 'kill 0' EXIT
cd ~/larachat
# NPMの開発サーバーをバックグラウンドで実行
npm run dev &
echo "Started npm run dev with PID $!"
# PHP Artisanのreverb:startをバックグラウンドで実行
php artisan reverb:start &
echo "Started php artisan reverb:start with PID $!"
# PHP Artisanのサーバーをバックグラウンドで実行
php artisan serve &
echo "Started php artisan serve with PID $!"
# PHP Artisanのキューをバックグラウンドで実行
php artisan queue:work &
echo "Started php artisan queue:work with PID $!"
# 全プロセスが終了するまで待機
wait

これは chatGPT さんに作ってもらいました。一瞬でこんなものを作ってくれました。
こういうことをやらせると無敵ですね。