本文へ移動
Laravel Tips

リアルタイムに届ける(ブロードキャスト)

サーバーで起きた出来事を、WebSocket を使ってブラウザへすぐ届けるブロードキャストのしくみと、チャンネル・認可・Echo・モデルの配信・クライアントイベントを説明します。

ブロードキャストは、サーバーで起きた出来事を、ページを開き直さなくても、ブラウザへすぐ知らせるしくみです。ラジオ放送のように、サーバーが「放送局」になり、聞きたい人(ブラウザ)が「チャンネル」を選んで聞きます。中では WebSocket(ブラウザとサーバーがつなぎっぱなしになって、お互いに好きなときにメッセージを送れる通信)を使います。サーバーに何度も「更新はありますか?」と聞きに行くより、むだが少なくなります。

たとえば、ユーザーのデータを CSV ファイルにして、メールで送る機能があるとします。作るのに数分かかるので、キュー(時間のかかる処理をあとで動かすしくみ)のジョブ(キューに入れる1つ1つの仕事)として動かします。終わったとき、App\Events\UserDataExported というイベントを放送すれば、ブラウザの JavaScript が受け取り、ページを更新しなくても「メールで送りました」と表示できます。

考え方は単純です。ブラウザは名前のついた「チャンネル」につなぎ、Laravel は、そのチャンネルへ「イベント」を放送します。イベントには、ブラウザに渡したいデータを入れられます。

Laravel のイベント(「〜が起きた」という知らせ)を、そのままブラウザの JavaScript へ放送できるので、サーバーとブラウザで、同じイベント名とデータを使えます。

使えるドライバー(放送のしくみ)#

サーバー側の放送のしくみ(ドライバー)は、はじめから4つ用意されています。

名前 説明
Laravel Reverb Laravel の公式の WebSocket サーバー
Pusher Channels Pusher 社のサービス
Ably Ably 社のサービス
Mercure Mercure のハブ(中継役)を使う方法

このほかに、手元の開発や確認のための log ドライバー(放送の内容を記録するだけ)と、テストで放送を止めるための null ドライバーがあります。

補足

ブロードキャストの前に、イベントとリスナーのページを読んでおいてください。

はじめかた(クイックスタート)#

新しい Laravel のアプリでは、はじめからブロードキャストは使えません。次の Artisan コマンド(php artisan で動かす Laravel のコマンド)で使えるようにします。

bash
php artisan install:broadcasting

このコマンドは、どの放送のしくみを使うかをたずねます。また、設定ファイル config/broadcasting.php と、放送の認可(聞いてよい人かの確認)を書く routes/channels.php を作ります。設定ファイルには、どのドライバーの設定例も入っています。

config/broadcasting.php が手元に無くても心配いりません。install:broadcasting を動かすと作られます。

放送を使えるようにしたら、あとは、放送するイベントを作ることと、ブラウザで受け取ることです。Laravel の React・Vue・Svelte のスターターキットを使っているなら、Echo の useEcho フック(画面の部品の中から呼ぶ関数)で受け取れます。

補足

イベントを放送する前に、キューのワーカー(キューから仕事を取り出して動かすプログラム)を用意して、動かしておきます。放送は、すべてキューのジョブとして動きます。放送のせいで、アプリの応答が遅くならないようにするためです。

サーバー側の設定#

放送は、サーバー側のドライバーが Laravel のイベントを放送し、ブラウザ側の Laravel Echo(JavaScript のライブラリ)が受け取る、という形で動きます。サーバー側に、設定とパッケージのインストールが必要です。

Reverb#

install:broadcasting に --reverb を付けると、Reverb に必要な Composer と NPM(PHP と JavaScript の部品を入れる道具)のパッケージ(部品)を入れ、.env に必要な値を足します。

bash
php artisan install:broadcasting --reverb

手でインストールするときは、次のようにします。

bash
composer require laravel/reverb

入れたら、Reverb のインストールコマンドで、設定ファイルの公開(アプリの中へ設定ファイルを写すこと)、.env への環境変数(環境ごとに変える設定値)の追加、放送の有効化をします。

bash
php artisan reverb:install

Reverb のくわしい使い方は、Reverb の公式ドキュメントにあります。

Pusher Channels#

--pusher を付けると、Pusher の認証情報をたずね、PHP と JavaScript の SDK(開発用の部品)を入れ、.env を更新します。

bash
php artisan install:broadcasting --pusher

手でインストールするときは、まず Pusher Channels の PHP SDK を入れます。

bash
composer require pusher/pusher-php-server

次に、config/broadcasting.php に認証情報を設定します。ふつうは .env に書きます。

ini
PUSHER_APP_ID="your-pusher-app-id"
PUSHER_APP_KEY="your-pusher-key"
PUSHER_APP_SECRET="your-pusher-secret"
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME="https"
PUSHER_APP_CLUSTER="mt1"

config/broadcasting.php の pusher の設定には、クラスター(使う Pusher のサーバーの地域。例の mt1 など)のように、Channels が対応する追加の options も書けます。

そして、.env の BROADCAST_CONNECTION を pusher にします。

ini
BROADCAST_CONNECTION=pusher

最後に、ブラウザ側で放送を受け取る Laravel Echo を設定します。

暗号化したプライベートチャンネルを使うとき#

エンドツーエンドで暗号化する(送る側と受け取る側だけが中身を読める)プライベートチャンネルを使うなら、pusher の options に、Base64 にした32バイトの鍵を、encryption_master_key_base64 として足します。

php
'options' => [
    // ...
    'encryption_master_key_base64' => env('PUSHER_ENCRYPTION_MASTER_KEY'),
],

鍵は、openssl コマンドで作れます。

bash
openssl rand -base64 32

Ably#

補足

ここでは、Ably を「Pusher 互換」のモードで使う方法を説明します。Ably のチームは、Ably の特長を生かせる、専用の放送のしくみと Echo のクライアント(ブラウザ側で受け取る部品)を作って勧めています。そちらを使うときは、Ably の Laravel 用ブロードキャスターのドキュメントを見てください。

--ably を付けると、Ably の認証情報をたずね、PHP と JavaScript の SDK を入れ、.env を更新します。

bash
php artisan install:broadcasting --ably

先に進む前に、Ably のアプリの設定で、Pusher プロトコルへの対応を有効にします。Ably の設定画面の「Protocol Adapter Settings」で有効にできます。

手でインストールするときは、Ably の PHP SDK を入れます。

bash
composer require ably/ably-php

次に、config/broadcasting.php に認証情報を設定します。キーは、ふつう ABLY_KEY という環境変数に書きます。

ini
ABLY_KEY=your-ably-key

そして、BROADCAST_CONNECTION を ably にします。

ini
BROADCAST_CONNECTION=ably

最後に、ブラウザ側の Laravel Echo を設定します。

Mercure#

--mercure を付けると、Mercure の認証情報をたずね、PHP と JavaScript の SDK を入れ、.env を更新します。

bash
php artisan install:broadcasting --mercure

手でインストールするときは、Symfony(PHP の別のフレームワーク)の Mercure の部品と、JWT(署名つきの通行証)のライブラリを入れます。

bash
composer require symfony/mercure:^0.8 web-token/jwt-library:^4.1

次に、.env に Mercure の接続を書きます。

ini
BROADCAST_CONNECTION=mercure

MERCURE_URL=https://mercure.example.com/.well-known/mercure
MERCURE_PUBLIC_URL=https://mercure.example.com/.well-known/mercure
MERCURE_JWT_SECRET=<your-mercure-jwt-secret>

MERCURE_URL は、Laravel が更新を送り出す URL です。MERCURE_PUBLIC_URL は、ブラウザが聞きに来る URL です。Mercure のハブ(中継役)にも、同じ JWT の秘密の値を設定します。

暗号化したプライベートチャンネルを使うなら、32バイトの MERCURE_ENCRYPTION_KEY も書きます。

ini
MERCURE_ENCRYPTION_KEY=<your-32-byte-encryption-key>

最後に、ブラウザ側の Laravel Echo を設定します。

ブラウザ側の設定#

Laravel Echo は、チャンネルにつなぎ、サーバーが放送したイベントを聞くための JavaScript のライブラリです。install:broadcasting で入れたときは、Echo の設定も自動で入ります。手で設定したいときは、次の手順にしたがいます。

Reverb の場合#

Reverb は、WebSocket のやりとりに Pusher と同じ決まりを使うので、pusher-js も入れます。

bash
npm install --save-dev laravel-echo pusher-js

入れたら、アプリの JavaScript で Echo を作ります。置く場所は、Laravel に入っている resources/js/app.js の末尾がよいです。

JavaScript の場合:

js
import Echo from 'laravel-echo';

import Pusher from 'pusher-js';
window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'reverb',
    key: import.meta.env.VITE_REVERB_APP_KEY,
    wsHost: import.meta.env.VITE_REVERB_HOST,
    wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
    wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
});

React の場合:

js
import { configureEcho } from "@laravel/echo-react";

configureEcho({
    broadcaster: "reverb",
    // key: import.meta.env.VITE_REVERB_APP_KEY,
    // wsHost: import.meta.env.VITE_REVERB_HOST,
    // wsPort: import.meta.env.VITE_REVERB_PORT,
    // wssPort: import.meta.env.VITE_REVERB_PORT,
    // forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    // enabledTransports: ['ws', 'wss'],
});

Vue の場合:

js
import { configureEcho } from "@laravel/echo-vue";

configureEcho({
    broadcaster: "reverb",
    // key: import.meta.env.VITE_REVERB_APP_KEY,
    // wsHost: import.meta.env.VITE_REVERB_HOST,
    // wsPort: import.meta.env.VITE_REVERB_PORT,
    // wssPort: import.meta.env.VITE_REVERB_PORT,
    // forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    // enabledTransports: ['ws', 'wss'],
});

Svelte の場合:

js
import { configureEcho } from "@laravel/echo-svelte";

configureEcho({
    broadcaster: "reverb",
    // key: import.meta.env.VITE_REVERB_APP_KEY,
    // wsHost: import.meta.env.VITE_REVERB_HOST,
    // wsPort: import.meta.env.VITE_REVERB_PORT,
    // wssPort: import.meta.env.VITE_REVERB_PORT,
    // forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    // enabledTransports: ['ws', 'wss'],
});

そのあと、アプリのアセット(JavaScript や CSS)をビルドします(公開できる形にまとめます)。

bash
npm run build

注意

Laravel Echo の reverb ブロードキャスターには、laravel-echo の v1.16.0 以上が必要です。

Pusher Channels の場合#

laravel-echo と pusher-js を入れます。

bash
npm install --save-dev laravel-echo pusher-js

resources/js/app.js で Echo を作ります。

JavaScript の場合:

js
import Echo from 'laravel-echo';

import Pusher from 'pusher-js';
window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: import.meta.env.VITE_PUSHER_APP_KEY,
    cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
    forceTLS: true
});

React の場合:

js
import { configureEcho } from "@laravel/echo-react";

configureEcho({
    broadcaster: "pusher",
    // key: import.meta.env.VITE_PUSHER_APP_KEY,
    // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
    // forceTLS: true,
    // wsHost: import.meta.env.VITE_PUSHER_HOST,
    // wsPort: import.meta.env.VITE_PUSHER_PORT,
    // wssPort: import.meta.env.VITE_PUSHER_PORT,
    // enabledTransports: ["ws", "wss"],
});

Vue の場合:

js
import { configureEcho } from "@laravel/echo-vue";

configureEcho({
    broadcaster: "pusher",
    // key: import.meta.env.VITE_PUSHER_APP_KEY,
    // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
    // forceTLS: true,
    // wsHost: import.meta.env.VITE_PUSHER_HOST,
    // wsPort: import.meta.env.VITE_PUSHER_PORT,
    // wssPort: import.meta.env.VITE_PUSHER_PORT,
    // enabledTransports: ["ws", "wss"],
});

Svelte の場合:

js
import { configureEcho } from "@laravel/echo-svelte";

configureEcho({
    broadcaster: "pusher",
    // key: import.meta.env.VITE_PUSHER_APP_KEY,
    // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
    // forceTLS: true,
    // wsHost: import.meta.env.VITE_PUSHER_HOST,
    // wsPort: import.meta.env.VITE_PUSHER_PORT,
    // wssPort: import.meta.env.VITE_PUSHER_PORT,
    // enabledTransports: ["ws", "wss"],
});

次に、.env に Pusher の環境変数を書きます。無ければ足します。

ini
PUSHER_APP_ID="your-pusher-app-id"
PUSHER_APP_KEY="your-pusher-key"
PUSHER_APP_SECRET="your-pusher-secret"
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME="https"
PUSHER_APP_CLUSTER="mt1"

VITE_APP_NAME="${APP_NAME}"
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

Echo の設定を整えたら、アセットをビルドします。

bash
npm run build

補足

JavaScript のビルドについては、Vite のページを見てください。

すでにあるクライアントを使う#

すでに設定した Pusher Channels のクライアントがあるなら、client というオプションで Echo に渡せます。

js
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

const options = {
    broadcaster: 'pusher',
    key: import.meta.env.VITE_PUSHER_APP_KEY
}

window.Echo = new Echo({
    ...options,
    client: new Pusher(options.key, options)
});

Ably の場合#

補足

ここでも、Ably を「Pusher 互換」のモードで使う方法を説明します。Ably 専用のクライアントを使うときは、Ably の Laravel 用ブロードキャスターのドキュメントを見てください。

laravel-echo と pusher-js を入れます。

bash
npm install --save-dev laravel-echo pusher-js

先に進む前に、Ably のアプリの設定で、Pusher プロトコルへの対応を有効にします(「Protocol Adapter Settings」)。

resources/js/app.js で Echo を作ります。

JavaScript の場合:

js
import Echo from 'laravel-echo';

import Pusher from 'pusher-js';
window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: import.meta.env.VITE_ABLY_PUBLIC_KEY,
    wsHost: 'realtime-pusher.ably.io',
    wsPort: 443,
    disableStats: true,
    encrypted: true,
});

React の場合:

js
import { configureEcho } from "@laravel/echo-react";

configureEcho({
    broadcaster: "ably",
    // key: import.meta.env.VITE_ABLY_PUBLIC_KEY,
    // wsHost: "realtime-pusher.ably.io",
    // wsPort: 443,
    // disableStats: true,
    // encrypted: true,
});

Vue の場合:

js
import { configureEcho } from "@laravel/echo-vue";

configureEcho({
    broadcaster: "ably",
    // key: import.meta.env.VITE_ABLY_PUBLIC_KEY,
    // wsHost: "realtime-pusher.ably.io",
    // wsPort: 443,
    // disableStats: true,
    // encrypted: true,
});

Svelte の場合:

js
import { configureEcho } from "@laravel/echo-svelte";

configureEcho({
    broadcaster: "ably",
    // key: import.meta.env.VITE_ABLY_PUBLIC_KEY,
    // wsHost: "realtime-pusher.ably.io",
    // wsPort: 443,
    // disableStats: true,
    // encrypted: true,
});

設定に出てくる VITE_ABLY_PUBLIC_KEY には、Ably の公開キーを入れます。公開キーは、Ably のキーのうち、: より前の部分です。

設定を整えたら、アセットをビルドします。

bash
npm run dev

補足

JavaScript のビルドについては、Vite のページを見てください。

Mercure の場合#

laravel-echo を入れます。

bash
npm install --save-dev laravel-echo

mercure を指定して Echo を作ります。host を省くと、いまのオリジン(開いているページの https:// などの種類・ドメイン・ポートの組)の /.well-known/mercure になります。

JavaScript の場合:

js
import Echo from 'laravel-echo';

window.Echo = new Echo({
    broadcaster: 'mercure',
    host: import.meta.env.VITE_MERCURE_HUB_URL,
});

React の場合:

js
import { configureEcho } from "@laravel/echo-react";

configureEcho({
    broadcaster: "mercure",
});

Vue の場合:

js
import { configureEcho } from "@laravel/echo-vue";

configureEcho({
    broadcaster: "mercure",
});

Svelte の場合:

js
import { configureEcho } from "@laravel/echo-svelte";

configureEcho({
    broadcaster: "mercure",
});

ハブの URL を .env に書きます。

ini
VITE_MERCURE_HUB_URL="${MERCURE_PUBLIC_URL}"

全体の流れ#

放送は、サーバーの Laravel のイベントを、ブラウザの JavaScript へ、ドライバー経由で届けるしくみです。ブラウザでは、Laravel Echo で受け取ります。

イベントは「チャンネル」を通って届きます。チャンネルには、公開と非公開があります。公開のチャンネルは、アプリを開いた人なら、ログインしていなくても聞けます。非公開(プライベート)のチャンネルは、ログインしていて、聞いてよいと確認された人だけが聞けます。

例:注文の配送状況#

ネットショップで、注文の配送状況を見るページがあるとします。配送状況が更新されると、OrderShipmentStatusUpdated というイベントが起きるとします。

php
use App\Events\OrderShipmentStatusUpdated;

OrderShipmentStatusUpdated::dispatch($order);

ShouldBroadcast を付ける#

注文のページを見ている人に、ページを開き直さなくても状況を見せたいので、更新のたびに放送します。そのため、イベントに ShouldBroadcast インターフェース(クラスが守る約束ごと)を付けます。付いたイベントは、起きたときに放送されます。

php
<?php

namespace App\Events;

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

class OrderShipmentStatusUpdated implements ShouldBroadcast
{
    /**
     * The order instance.
     *
     * @var \App\Models\Order
     */
    public $order;
}

ShouldBroadcast を付けると、broadcastOn メソッドを書く必要があります。どのチャンネルへ放送するかを返すメソッドです。コマンドで作ったイベントのクラスには、中身が空の broadcastOn が最初から入っているので、中身を書くだけです。注文した本人だけに見せたいので、注文に結びついたプライベートチャンネルへ放送します。

php
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PrivateChannel;

/**
 * Get the channel the event should broadcast on.
 */
public function broadcastOn(): Channel
{
    return new PrivateChannel('orders.'.$this->order->id);
}

複数のチャンネルへ放送したいときは、配列を返します。

php
use Illuminate\Broadcasting\PrivateChannel;

/**
 * Get the channels the event should broadcast on.
 *
 * @return array<int, \Illuminate\Broadcasting\Channel>
 */
public function broadcastOn(): array
{
    return [
        new PrivateChannel('orders.'.$this->order->id),
        // ...
    ];
}

チャンネルの認可#

プライベートチャンネルは、聞いてよい人かの確認(認可)が要ります。ルールは routes/channels.php に書きます。次の例は、orders.1 のようなチャンネルを聞こうとする人が、その注文を作った人本人かを確かめます。

php
use App\Models\Order;
use App\Models\User;

Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) {
    return $user->id === Order::findOrNew($orderId)->user_id;
});

channel メソッドは、チャンネルの名前と、true か false(聞いてよいか)を返す関数を受け取ります。どの認可の関数にも、いまログインしている人が最初の引数として渡されます。名前の {orderId} のような部分(ワイルドカード)の値は、そのあとの引数として渡されます。

ブラウザで受け取る#

あとは、JavaScript でイベントを聞くだけです。React・Vue・Svelte には、Echo のフックがあり、簡単に始められます。はじめのままでは、イベントの public な(外から見える)プロパティ(クラスの中の変数)が、すべて放送の中身に入ります。

React の場合:

js
import { useEcho } from "@laravel/echo-react";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);

Vue の場合:

vue
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
</script>

Svelte の場合:

svelte
<script>
import { useEcho } from "@laravel/echo-svelte";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
</script>

放送するイベントを作る#

イベントを放送するには、イベントのクラスに Illuminate\Contracts\Broadcasting\ShouldBroadcast を付けます。Laravel が作ったイベントのクラスには、すでにこのインターフェースが読みこまれているので、書き足すだけです。

ShouldBroadcast には、broadcastOn というメソッドが1つ必要です。放送するチャンネル(またはチャンネルの配列)を返します。チャンネルには、次の3種類があります。

名前 説明
Channel 公開のチャンネル。だれでも聞ける
PrivateChannel 非公開のチャンネル。認可が要る
PresenceChannel 非公開で、だれが聞いているかも分かるチャンネル。認可が要る
php
<?php

namespace App\Events;

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

class ServerCreated implements ShouldBroadcast
{
    use SerializesModels;

    /**
     * Create a new event instance.
     */
    public function __construct(
        public User $user,
    ) {}

    /**
     * Get the channels the event should broadcast on.
     *
     * @return array<int, \Illuminate\Broadcasting\Channel>
     */
    public function broadcastOn(): array
    {
        return [
            new PrivateChannel('user.'.$this->user->id),
        ];
    }
}

ShouldBroadcast を付けたら、あとはふつうにイベントを起こすだけです。起きると、キューのジョブが、設定したドライバーで自動的に放送します。

放送の名前#

はじめのままでは、イベントのクラス名が放送の名前になります。broadcastAs メソッドで、名前を変えられます。

php
/**
 * The event's broadcast name.
 */
public function broadcastAs(): string
{
    return 'server.created';
}

broadcastAs で名前を変えたときは、ブラウザで聞くときの名前の先頭に . を付けます。Echo が、名前の前にアプリの名前空間(App\Events のような、クラスの住所)を付けないようにするためです。

js
.listen('.server.created', function (e) {
    // ...
});

放送するデータ#

放送されるとき、イベントの public なプロパティは、すべて自動で変換されて、放送の中身になります。ブラウザの JavaScript から、そのまま使えます。たとえば、public なプロパティが、Eloquent のモデルが入った $user 1つだけなら、放送の中身は次のようになります。

json
{
    "user": {
        "id": 1,
        "name": "Patrick Stewart"
        ...
    }
}

中身を細かく決めたいときは、イベントに broadcastWith メソッドを足します。放送したい配列を返します。

php
/**
 * Get the data to broadcast.
 *
 * @return array<string, mixed>
 */
public function broadcastWith(): array
{
    return ['id' => $this->user->id];
}

放送のキュー#

はじめのままでは、放送のイベントは、queue.php で決めた標準の接続の、標準のキューに入ります。キューの接続と名前は、イベントのクラスに、Connection と Queue という PHP の属性(クラスの前に書く #[...] の印)で指定して変えられます。

php
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Queue;

#[Connection('redis')]
#[Queue('default')]
class ServerCreated implements ShouldBroadcast
{
    // ...
}

キューの名前だけなら、broadcastQueue メソッドを定義しても変えられます。

php
/**
 * The name of the queue on which to place the broadcasting job.
 */
public function broadcastQueue(): string
{
    return 'default';
}

放送するイベントすべてを、同じキューにしたいなら、1つ1つ指定せずに、ShouldBroadcast をキューへ振り分けることもできます。

標準のキューではなく、sync(すぐその場で実行するキュー)で放送したいときは、ShouldBroadcast の代わりに ShouldBroadcastNow を付けます。

php
<?php

namespace App\Events;

use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;

class OrderShipmentStatusUpdated implements ShouldBroadcastNow
{
    // ...
}

放送する条件#

条件が合うときだけ放送したいときは、broadcastWhen メソッドを足します。

php
/**
 * Determine if this event should broadcast.
 */
public function broadcastWhen(): bool
{
    return $this->order->value > 100;
}

データベースのトランザクションとの関係#

放送のイベントが、データベースのトランザクション(まとめて成功か失敗かを決める操作)の中で起きると、トランザクションが確定する前に、キューが処理してしまうことがあります。すると、トランザクションの中で変えたデータが、まだデータベースに反映されていなかったり、作ったはずのデータが無かったりします。そのデータに頼るイベントは、放送のジョブが動いたときに、思わぬエラーになります。

キューの接続の設定 after_commit が false のときでも、イベントのクラスに ShouldDispatchAfterCommit を付ければ、開いているトランザクションがすべて確定したあとに放送するよう決められます。

php
<?php

namespace App\Events;

use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Queue\SerializesModels;

class ServerCreated implements ShouldBroadcast, ShouldDispatchAfterCommit
{
    use SerializesModels;
}

補足

この問題への対処は、キューのジョブとデータベースのトランザクションの説明にも書かれています。

チャンネルの認可を決める#

プライベートチャンネルは、いまログインしている人が、本当に聞いてよいかの確認(認可)が要ります。確認は、チャンネルの名前を付けた HTTP リクエストを Laravel に送り、アプリに決めさせる形で確かめます。Laravel Echo を使えば、プライベートチャンネルの確認のリクエストは自動で送られます。

放送をインストールすると、Laravel は、確認のリクエストを受ける /broadcasting/auth というルートを自動で登録しようとします。自動で登録されなかったときは、/bootstrap/app.php の withRouting に channels を足して、自分で登録します。

php
->withRouting(
    web: __DIR__.'/../routes/web.php',
    channels: __DIR__.'/../routes/channels.php',
    health: '/up',
)

認可の関数を書く#

どの人が、どのチャンネルを聞いてよいかを決めるのは、install:broadcasting が作る routes/channels.php です。Broadcast::channel メソッドで、認可の関数を登録します。

php
use App\Models\User;

Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) {
    return $user->id === Order::findOrNew($orderId)->user_id;
});

channel メソッドは、チャンネルの名前と、true か false を返す関数を受け取ります。関数には、いまログインしている人が最初の引数として渡され、名前の {orderId} のようなワイルドカードの値が、そのあとの引数として渡されます。

登録した認可の関数の一覧は、channel:list という Artisan コマンドで見られます。

bash
php artisan channel:list

モデルバインディング#

HTTP のルートと同じように、チャンネルのルートでも、暗黙のモデルバインディングと明示的なモデルバインディング(名前の中の ID から、モデルを自動で探して渡すしくみ。ルーティングのページを参照)が使えます。文字や数字の注文 ID の代わりに、Order のモデルそのものを受け取れます。

php
use App\Models\Order;
use App\Models\User;

Broadcast::channel('orders.{order}', function (User $user, Order $order) {
    return $user->id === $order->user_id;
});

注意

HTTP のルートのモデルバインディングとちがい、チャンネルのモデルバインディングは、暗黙のモデルバインディングのスコープ(探す範囲を絞る機能)には対応していません。ただ、ほとんどのチャンネルは、モデル1つの主キー(その行だけの番号)で絞れるので、困ることは少ないです。

認証#

プライベートと presence のチャンネルは、アプリの標準の認証ガード(ログインを確かめる係)で、いまの人を確かめます。ログインしていなければ、認可は自動で断られ、認可の関数は動きません。必要なら、リクエストを確かめるガードを複数、指定できます。

php
Broadcast::channel('channel', function () {
    // ...
}, ['guards' => ['web', 'admin']]);

チャンネルのクラスを使う#

使うチャンネルがたくさんあると、routes/channels.php が長くなります。そんなときは、関数の代わりに、チャンネルのクラスで認可できます。make:channel という Artisan コマンドで、App/Broadcasting に作られます。

bash
php artisan make:channel OrderChannel

routes/channels.php に、チャンネルを登録します。

php
use App\Broadcasting\OrderChannel;

Broadcast::channel('orders.{order}', OrderChannel::class);

認可の中身は、チャンネルのクラスの join メソッドに書きます。関数に書くはずだった内容と同じです。モデルバインディングも使えます。

php
<?php

namespace App\Broadcasting;

use App\Models\Order;
use App\Models\User;

class OrderChannel
{
    /**
     * Create a new channel instance.
     */
    public function __construct() {}

    /**
     * Authenticate the user's access to the channel.
     */
    public function join(User $user, Order $order): array|bool
    {
        return $user->id === $order->user_id;
    }
}

補足

Laravel の多くのクラスと同じく、チャンネルのクラスもサービスコンテナが作ります。必要な道具は、コンストラクタ(オブジェクトを作るときに最初に動くメソッド)に型を書いておけば、渡してもらえます。

イベントを放送する#

イベントを作って ShouldBroadcast を付けたら、イベントの dispatch メソッドで起こすだけです。イベントの係が ShouldBroadcast に気づいて、放送のためにキューへ入れます。

php
use App\Events\OrderShipmentStatusUpdated;

OrderShipmentStatusUpdated::dispatch($order);

自分以外にだけ放送する#

チャンネルを聞いている人のうち、いま操作している本人だけ除いて放送したいときがあります。broadcast ヘルパーと toOthers メソッドを使います。

php
use App\Events\OrderShipmentStatusUpdated;

broadcast(new OrderShipmentStatusUpdated($update))->toOthers();

使いどきを、タスク管理のアプリで考えます。タスク名を入れて、新しいタスクを作るとき、/task へリクエストを送り、作ったことを放送して、できたタスクを JSON で返すとします。JavaScript は、返事を受け取ると、そのままタスクの一覧に足すかもしれません。

js
axios.post('/task', task)
    .then((response) => {
        this.tasks.push(response.data);
    });

ところが、作ったことは放送もしています。JavaScript が、この放送も聞いて一覧に足していたら、一覧に同じタスクが2つできてしまいます(返事の分と、放送の分)。toOthers を使えば、いまの人には放送しないようにできます。

注意

toOthers を使うイベントは、Illuminate\Broadcasting\InteractsWithSockets というトレイト(機能をまとめて差しこむ部品)を使う必要があります。

設定#

Laravel Echo を作ると、接続にソケット ID(接続の番号)が付きます。HTTP リクエストを送るのに、アプリ全体で共通の Axios(JavaScript からリクエストを送るライブラリ)を使っていれば、ソケット ID は、送るリクエストすべてに X-Socket-ID というヘッダーとして自動で付きます。toOthers を呼ぶと、Laravel はヘッダーからソケット ID を取り出し、その接続には放送しないよう、放送の係に伝えます。

共通の Axios を使っていないときは、すべてのリクエストに X-Socket-ID ヘッダーを付けるよう、自分で設定します。ソケット ID は Echo.socketId で取り出せます。

js
var socketId = Echo.socketId();

接続を選ぶ#

放送の接続が複数あるとき、標準とはちがう接続で放送したいなら、via メソッドで指定します。

php
use App\Events\OrderShipmentStatusUpdated;

broadcast(new OrderShipmentStatusUpdated($update))->via('pusher');

イベントのコンストラクタの中で broadcastVia を呼んでも、接続を決められます。そのときは、イベントのクラスに InteractsWithBroadcasting というトレイトを使います。

php
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithBroadcasting;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;

class OrderShipmentStatusUpdated implements ShouldBroadcast
{
    use InteractsWithBroadcasting;

    /**
     * Create a new event instance.
     */
    public function __construct()
    {
        $this->broadcastVia('pusher');
    }
}

無名のイベント#

簡単なイベントを、専用のクラスを作らずに放送したいときがあります。Broadcast ファサード(Broadcast:: のように、クラス名と :: で機能を呼べる窓口)で「無名のイベント」を放送できます。

php
Broadcast::on('orders.'.$order->id)->send();

上の例は、次のようなイベントを放送します。

json
{
    "event": "AnonymousEvent",
    "data": "[]",
    "channel": "orders.1"
}

as と with で、イベントの名前とデータを変えられます。

php
Broadcast::on('orders.'.$order->id)
    ->as('OrderPlaced')
    ->with($order)
    ->send();

この例は、次のようなイベントを放送します。

json
{
    "event": "OrderPlaced",
    "data": "{ id: 1, total: 100 }",
    "channel": "orders.1"
}

プライベートや presence のチャンネルへ放送したいときは、private と presence を使います。

php
Broadcast::private('orders.'.$order->id)->send();
Broadcast::presence('channels.'.$channel->id)->send();

send は、アプリのキューに入れて処理します。すぐに放送したいなら、sendNow を使います。

php
Broadcast::on('orders.'.$order->id)->sendNow();

いまログインしている人以外にだけ放送するなら、toOthers を呼びます。

php
Broadcast::on('orders.'.$order->id)
    ->toOthers()
    ->send();

放送の失敗で止まらないようにする#

キューのサーバーが使えないときや、放送でエラーが起きたとき、例外(エラーを知らせるしくみ)が投げられて、利用者の画面にアプリのエラーが出ることがあります。放送は、アプリの中心の機能を助ける役目のことが多いので、放送の失敗で利用者の邪魔をしないようにもできます。イベントに ShouldRescue インターフェースを付けます。

ShouldRescue を付けたイベントは、放送のとき、Laravel の rescue ヘルパー関数を自動で使います。このヘルパーは、例外をつかまえて、アプリの例外ハンドラー(例外をまとめて受け持つ係)に報告(ログに記録)し、アプリはふつうにつづきます。

php
<?php

namespace App\Events;

use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Broadcasting\ShouldRescue;

class ServerCreated implements ShouldBroadcast, ShouldRescue
{
    // ...
}

放送を受け取る(ブラウザ側)#

イベントを聞く#

Laravel Echo を入れて作ったら、Laravel が放送したイベントを聞けます。まず channel メソッドでチャンネルを取り出し、listen メソッドで、聞きたいイベントを指定します。

js
Echo.channel(`orders.${this.order.id}`)
    .listen('OrderShipmentStatusUpdated', (e) => {
        console.log(e.order.name);
    });

プライベートチャンネルなら、channel の代わりに private を使います。listen を続けて書くと、1つのチャンネルで、複数のイベントを聞けます。

js
Echo.private(`orders.${this.order.id}`)
    .listen(/* ... */)
    .listen(/* ... */)
    .listen(/* ... */);

イベントを聞くのをやめる#

チャンネルからは出ずに、特定のイベントを聞くのだけやめたいときは、stopListening を使います。

js
Echo.private(`orders.${this.order.id}`)
    .stopListening('OrderShipmentStatusUpdated');

チャンネルから出る#

チャンネルから出るには、Echo の leaveChannel を呼びます。

js
Echo.leaveChannel(`orders.${this.order.id}`);

そのチャンネルと、結びついたプライベートと presence のチャンネルからも出たいなら、leave を呼びます。

js
Echo.leave(`orders.${this.order.id}`);

名前空間#

ここまでの例では、イベントのクラスの名前空間(App\Events)を書いていません。Echo が、イベントは App\Events にあると考えるからです。この基準の名前空間は、Echo を作るときに namespace というオプションで変えられます。

js
window.Echo = new Echo({
    broadcaster: 'pusher',
    // ...
    namespace: 'App.Other.Namespace'
});

Echo でイベントを聞くとき、クラスの名前の先頭に . を付けても、名前空間をはぶかずに、完全な名前を書けます。

js
Echo.channel('orders')
    .listen('.Namespace\\Event\\Class', (e) => {
        // ...
    });

React・Vue・Svelte で使う#

Laravel Echo には、React・Vue・Svelte のフックがあり、イベントを簡単に聞けます。まず、プライベートのイベントを聞く useEcho フックを呼びます。フックを使っている部品(コンポーネント)が画面から外れると、チャンネルからも自動で出ます。

React の場合:

js
import { useEcho } from "@laravel/echo-react";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);

Vue の場合:

vue
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
</script>

Svelte の場合:

svelte
<script>
import { useEcho } from "@laravel/echo-svelte";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
</script>

イベントの配列を渡すと、複数のイベントを聞けます。

js
useEcho(
    `orders.${orderId}`,
    ["OrderShipmentStatusUpdated", "OrderShipped"],
    (e) => {
        console.log(e.order);
    },
);

放送の中身の形(型)も指定できます。まちがった使い方に気づきやすくなり、エディタでも書きやすくなります。

ts
type OrderData = {
    order: {
        id: number;
        user: {
            id: number;
            name: string;
        };
        created_at: string;
    };
};

useEcho<OrderData>(`orders.${orderId}`, "OrderShipmentStatusUpdated", (e) => {
    console.log(e.order.id);
    console.log(e.order.user.id);
});

useEcho は、部品が画面から外れるとチャンネルから出ますが、返ってくる関数を使えば、自分で聞くのをやめたり、始めたりできます。

React の場合:

js
import { useEcho } from "@laravel/echo-react";

const { leaveChannel, leave, stopListening, listen } = useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);

// Stop listening without leaving channel...
stopListening();

// Start listening again...
listen();

// Leave channel...
leaveChannel();

// Leave a channel and also its associated private and presence channels...
leave();

Vue の場合:

vue
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";

const { leaveChannel, leave, stopListening, listen } = useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);

// Stop listening without leaving channel...
stopListening();

// Start listening again...
listen();

// Leave channel...
leaveChannel();

// Leave a channel and also its associated private and presence channels...
leave();
</script>

Svelte の場合:

svelte
<script>
import { useEcho } from "@laravel/echo-svelte";

const { leaveChannel, leave, stopListening, listen } = useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);

// Stop listening without leaving channel...
stopListening();

// Start listening again...
listen();

// Leave channel...
leaveChannel();

// Leave a channel and also its associated private and presence channels...
leave();
</script>

返ってくる関数の働きは、次のとおりです。

名前 説明
listen 聞くのを、また始める
stopListening チャンネルには残ったまま、聞くのをやめる
leaveChannel チャンネルから出る
leave チャンネルと、結びついたプライベートと presence のチャンネルから出る

公開チャンネルにつなぐ#

公開チャンネルには、useEchoPublic フックを使います。

React の場合:

js
import { useEchoPublic } from "@laravel/echo-react";

useEchoPublic("posts", "PostPublished", (e) => {
    console.log(e.post);
});

Vue の場合:

vue
<script setup lang="ts">
import { useEchoPublic } from "@laravel/echo-vue";

useEchoPublic("posts", "PostPublished", (e) => {
    console.log(e.post);
});
</script>

Svelte の場合:

svelte
<script>
import { useEchoPublic } from "@laravel/echo-svelte";

useEchoPublic("posts", "PostPublished", (e) => {
    console.log(e.post);
});
</script>

presence チャンネルにつなぐ#

presence チャンネルには、useEchoPresence フックを使います。

React の場合:

js
import { useEchoPresence } from "@laravel/echo-react";

useEchoPresence("posts", "PostPublished", (e) => {
    console.log(e.post);
});

Vue の場合:

vue
<script setup lang="ts">
import { useEchoPresence } from "@laravel/echo-vue";

useEchoPresence("posts", "PostPublished", (e) => {
    console.log(e.post);
});
</script>

Svelte の場合:

svelte
<script>
import { useEchoPresence } from "@laravel/echo-svelte";

useEchoPresence("posts", "PostPublished", (e) => {
    console.log(e.post);
});
</script>

つながっているかの状態#

useConnectionStatus フックで、いまの WebSocket のつながり具合が分かります。状態が変わると、値も自動で変わります。

React の場合:

jsx
import { useConnectionStatus } from "@laravel/echo-react";

function ConnectionIndicator() {
    const status = useConnectionStatus();

    return <div>Connection: {status}</div>;
}

Vue の場合:

vue
<script setup lang="ts">
import { useConnectionStatus } from "@laravel/echo-vue";

const status = useConnectionStatus();
</script>

<template>
    <div>Connection: {{ status }}</div>
</template>

Svelte の場合:

svelte
<script>
import { useConnectionStatus } from "@laravel/echo-svelte";

const status = useConnectionStatus();
</script>

<div>Connection: {status()}</div>

状態の値は、次のとおりです。

値 説明
connected WebSocket のサーバーにつながっている
connecting はじめてのつなぎ方を試している最中
reconnecting 切れたあとで、つなぎ直している最中
disconnected つながっておらず、つなぎ直そうともしていない
failed つなぐのに失敗し、もう試さない

ソケット ID#

useSocketId フックで、いまのソケット ID が分かります。つなぎ直して新しい ID になると、値も自動で変わります。

React の場合:

jsx
import { useSocketId } from "@laravel/echo-react";

function SocketIndicator() {
    const socketId = useSocketId();

    return <div>Socket ID: {socketId}</div>;
}

Vue の場合:

vue
<script setup lang="ts">
import { useSocketId } from "@laravel/echo-vue";

const socketId = useSocketId();
</script>

<template>
    <div>Socket ID: {{ socketId }}</div>
</template>

Svelte の場合:

svelte
<script>
import { useSocketId } from "@laravel/echo-svelte";

const socketId = useSocketId();
</script>

<div>Socket ID: {socketId()}</div>

フックの一覧#

名前 説明
useEcho プライベートチャンネルのイベントを聞く。返り値で、聞く・やめる・出るを操作できる
useEchoPublic 公開チャンネルのイベントを聞く
useEchoPresence presence チャンネルのイベントを聞く
useEchoModel モデルの放送を聞く(下の「モデルの放送」を参照)
useConnectionStatus WebSocket のつながりの状態を返す
useSocketId いまのソケット ID を返す

presence チャンネル#

presence チャンネルは、プライベートチャンネルの安全さに加えて、「いま、だれが聞いているか」が分かるチャンネルです。同じページを見ている人を知らせたり、チャットルームにいる人の一覧を出したりするような、みんなで使う機能を作りやすくなります。

presence チャンネルの認可#

presence チャンネルも、プライベートチャンネルの一種なので、認可が要ります。ただし、認可の関数は、参加を許すときに true を返しません。代わりに、その人についてのデータを配列で返します。

返したデータは、ブラウザの JavaScript の、presence チャンネルのイベントを聞く関数で使えます。参加を許さないときは、false か null を返します。

php
use App\Models\User;

Broadcast::channel('chat.{roomId}', function (User $user, int $roomId) {
    if ($user->canJoinRoom($roomId)) {
        return ['id' => $user->id, 'name' => $user->name];
    }
});

presence チャンネルに入る#

presence チャンネルには、Echo の join メソッドで入ります。join は、PresenceChannel を返します。listen に加えて、here・joining・leaving のイベントも聞けます。

js
Echo.join(`chat.${roomId}`)
    .here((users) => {
        // ...
    })
    .joining((user) => {
        console.log(user.name);
    })
    .leaving((user) => {
        console.log(user.name);
    })
    .error((error) => {
        console.error(error);
    });
名前 説明
here チャンネルに入れたら、すぐに実行される。いまチャンネルにいる、ほかの人たちの情報の配列を受け取る
joining 新しい人が入ってきたときに実行される
leaving 人が出ていったときに実行される
error 確認の窓口が 200 以外の HTTP ステータスを返したときや、返ってきた JSON を読みとれなかったときに実行される

presence チャンネルへ放送する#

presence チャンネルも、公開やプライベートのチャンネルと同じように、イベントを受け取れます。チャットルームの例で、NewMessage というイベントを、ルームの presence チャンネルへ放送するなら、イベントの broadcastOn から PresenceChannel を返します。

php
/**
 * Get the channels the event should broadcast on.
 *
 * @return array<int, \Illuminate\Broadcasting\Channel>
 */
public function broadcastOn(): array
{
    return [
        new PresenceChannel('chat.'.$this->message->room_id),
    ];
}

ほかのイベントと同じように、broadcast ヘルパーと toOthers で、いまの人を放送から外せます。

php
broadcast(new NewMessage($message));

broadcast(new NewMessage($message))->toOthers();

presence チャンネルに送ったイベントも、ほかのイベントと同じように、Echo の listen で聞けます。

js
Echo.join(`chat.${roomId}`)
    .here(/* ... */)
    .joining(/* ... */)
    .leaving(/* ... */)
    .listen('NewMessage', (e) => {
        // ...
    });

暗号化したプライベートチャンネル#

プライベートチャンネルは、聞ける人を、認可された人だけにします。ただし、イベントのデータそのものは、放送のサービスを、暗号化されないまま通ります。Pusher Channels や Mercure では、エンドツーエンドで暗号化するプライベートチャンネルを使えます。アプリと、認可されたクライアントだけが、イベントのデータを読めます。

始めるには、Pusher Channels か Mercure に、暗号化の鍵を設定します。そして、イベントの broadcastOn から、EncryptedPrivateChannel を返します。

php
use Illuminate\Broadcasting\EncryptedPrivateChannel;

/**
 * Get the channels the event should broadcast on.
 *
 * @return array<int, \Illuminate\Broadcasting\Channel>
 */
public function broadcastOn(): array
{
    return [
        new EncryptedPrivateChannel('orders.'.$this->order->id),
    ];
}

暗号化したプライベートチャンネルの認可は、ふつうのプライベートチャンネルとまったく同じです。routes/channels.php の orders.{orderId} の認可の関数が、暗号化した orders.1 のチャンネルの認可にもなります。

JavaScript では、Echo の encryptedPrivate メソッドで聞きます。

js
Echo.encryptedPrivate(`orders.${orderId}`)
    .listen('OrderShipmentStatusUpdated', (e) => {
        console.log(e.order);
    });

Pusher Channels を使うとき、ふつうの pusher-js には、暗号を解く部分が入っていません。Echo を設定するときに、with-encryption の版を読みこみます。

js
import Pusher from 'pusher-js/with-encryption';
window.Pusher = Pusher;

モデルの放送#

注意

ここから先を読む前に、放送の全体の考え方と、放送のイベントを自分で作って聞く方法を、先に知っておくことをおすすめします。

Eloquent のモデルが、作られたとき・更新されたとき・消されたときに、イベントを放送したいことはよくあります。モデルの状態の変化のイベントを自分で作り、ShouldBroadcast を付ければできます。

ただ、放送のためだけにイベントのクラスを作るのは、めんどうです。そこで Laravel では、モデルが状態の変化を自動で放送するよう、決められます。

まず、モデルに Illuminate\Database\Eloquent\BroadcastsEvents というトレイトを使い、さらに、放送するチャンネルの配列を返す broadcastOn メソッドを書きます。

php
<?php

namespace App\Models;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Database\Eloquent\BroadcastsEvents;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Post extends Model
{
    use BroadcastsEvents, HasFactory;

    /**
     * Get the user that the post belongs to.
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    /**
     * Get the channels that model events should broadcast on.
     *
     * @return array<int, \Illuminate\Broadcasting\Channel|\Illuminate\Database\Eloquent\Model>
     */
    public function broadcastOn(string $event): array
    {
        return [$this, $this->user];
    }
}

トレイトを使い、放送のチャンネルを決めると、モデルが作られたとき・更新されたとき・消されたとき・ごみ箱に入れられたとき・元に戻されたときに、自動でイベントが放送されます。

broadcastOn が受け取る文字列の $event には、モデルで起きたイベントの種類が入ります。値は created・updated・deleted・trashed・restored のどれかです。この値を見て、そのイベントのときに、どのチャンネルへ放送するか(あるいは放送しないか)を決められます。

php
/**
 * Get the channels that model events should broadcast on.
 *
 * @return array<string, array<int, \Illuminate\Broadcasting\Channel|\Illuminate\Database\Eloquent\Model>>
 */
public function broadcastOn(string $event): array
{
    return match ($event) {
        'deleted' => [],
        default => [$this, $this->user],
    };
}

放送するイベントの作りを変える#

モデルの放送のイベントの作りを、変えたいときがあります。モデルに newBroadcastableEvent メソッドを書きます。Illuminate\Database\Eloquent\BroadcastableModelEventOccurred を返します。

php
use Illuminate\Database\Eloquent\BroadcastableModelEventOccurred;

/**
 * Create a new broadcastable model event for the model.
 */
protected function newBroadcastableEvent(string $event): BroadcastableModelEventOccurred
{
    return (new BroadcastableModelEventOccurred(
        $this, $event
    ))->dontBroadcastToCurrentUser();
}

モデルの放送の約束ごと#

チャンネルの決まり#

上の例の broadcastOn は、Channel ではなく、Eloquent のモデルをそのまま返していました。モデルが返されると(配列の中にあっても)、Laravel は、モデルのクラス名と主キーから、プライベートチャンネルを自動で作ります。

たとえば、id が 1 の App\Models\User は、App.Models.User.1 という名前の Illuminate\Broadcasting\PrivateChannel になります。チャンネルの名前を完全に自分で決めたいなら、Channel を返してもかまいません。

php
use Illuminate\Broadcasting\PrivateChannel;

/**
 * Get the channels that model events should broadcast on.
 *
 * @return array<int, \Illuminate\Broadcasting\Channel>
 */
public function broadcastOn(string $event): array
{
    return [
        new PrivateChannel('user.'.$this->id)
    ];
}

チャンネルを自分で返すときも、コンストラクタにモデルを渡せます。そのときは、上のモデルの決まりで、チャンネルの名前の文字列になります。

php
return [new Channel($this->user)];

モデルのチャンネルの名前が知りたいときは、モデルの broadcastChannel を呼びます。id が 1 の App\Models\User なら、App.Models.User.1 が返ります。

php
$user->broadcastChannel();

イベントの決まり#

モデルの放送のイベントは、App\Events にある「本物の」イベントではありません。そのため、名前と中身は、決まりで決められます。放送の名前は、モデルのクラス名(名前空間は付けない)と、起きたモデルのイベントの名前をつなげたものです。

たとえば、App\Models\Post の更新は、ブラウザには PostUpdated というイベントとして放送され、中身は次のようになります。

json
{
    "model": {
        "id": 1,
        "title": "My first post"
        ...
    },
    ...
    "socket": "someSocketId"
}

App\Models\User を消したときは、UserDeleted というイベントが放送されます。

名前と中身を自分で決めたいなら、モデルに broadcastAs と broadcastWith を書きます。どちらも、起きているモデルのイベント(操作)の名前を受け取るので、操作ごとに、名前と中身を変えられます。broadcastAs が null を返したときは、Laravel は、上の決まりの名前で放送します。

php
/**
 * The model event's broadcast name.
 */
public function broadcastAs(string $event): string|null
{
    return match ($event) {
        'created' => 'post.created',
        default => null,
    };
}

/**
 * Get the data to broadcast for the model.
 *
 * @return array<string, mixed>
 */
public function broadcastWith(string $event): array
{
    return match ($event) {
        'created' => ['title' => $this->title],
        default => ['model' => $this],
    };
}

モデルの放送を聞く#

モデルに BroadcastsEvents トレイトを使い、broadcastOn を書いたら、ブラウザでモデルのイベントを聞けます。聞きかたの全体は、イベントを聞くの説明を見てください。

まず private でチャンネルを取り出し、listen で聞きたいイベントを指定します。チャンネルの名前は、モデルの放送の決まりに合わせます。

モデルのイベントは、App\Events にある本物のイベントではないので、聞くときの名前の先頭に . を付けます(特定の名前空間に属さない、という印です)。どのモデルのイベントにも、放送できるモデルのプロパティがすべて入った model というプロパティがあります。

js
Echo.private(`App.Models.User.${this.user.id}`)
    .listen('.UserUpdated', (e) => {
        console.log(e.model);
    });

React・Vue・Svelte で使う#

React・Vue・Svelte なら、Echo の useEchoModel フックで、モデルの放送を聞けます。

React の場合:

js
import { useEchoModel } from "@laravel/echo-react";

useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => {
    console.log(e.model);
});

Vue の場合:

vue
<script setup lang="ts">
import { useEchoModel } from "@laravel/echo-vue";

useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => {
    console.log(e.model);
});
</script>

Svelte の場合:

svelte
<script>
import { useEchoModel } from "@laravel/echo-svelte";

useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => {
    console.log(e.model);
});
</script>

モデルの放送の中身の形も、指定できます。

ts
type User = {
    id: number;
    name: string;
    email: string;
};

useEchoModel<User, "App.Models.User">("App.Models.User", userId, ["UserUpdated"], (e) => {
    console.log(e.model.id);
    console.log(e.model.name);
});

クライアントイベント#

補足

Pusher Channels で、クライアントイベントを送るには、Pusher の管理画面の「App Settings」で、「Client Events」を有効にします。

Laravel のアプリを通さずに、つながっているほかのクライアントへ、イベントを放送したいときがあります。たとえば、「入力中です」という知らせです。ある人がメッセージを打っていることを、同じ画面のほかの人に知らせる、といった使い方です。

クライアントイベントは、Echo の whisper メソッドで送ります。

JavaScript の場合:

js
Echo.private(`chat.${roomId}`)
    .whisper('typing', {
        name: this.user.name
    });

React の場合:

js
import { useEcho } from "@laravel/echo-react";

const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
    console.log('Chat event received:', e);
});

channel().whisper('typing', { name: user.name });

Vue の場合:

vue
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";

const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
    console.log('Chat event received:', e);
});

channel().whisper('typing', { name: user.name });
</script>

Svelte の場合:

svelte
<script>
import { useEcho } from "@laravel/echo-svelte";

const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
    console.log('Chat event received:', e);
});

channel().whisper('typing', { name: user.name });
</script>

クライアントイベントを聞くには、listenForWhisper メソッドを使います。

JavaScript の場合:

js
Echo.private(`chat.${roomId}`)
    .listenForWhisper('typing', (e) => {
        console.log(e.name);
    });

React の場合:

js
import { useEcho } from "@laravel/echo-react";

const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
    console.log('Chat event received:', e);
});

channel().listenForWhisper('typing', (e) => {
    console.log(e.name);
});

Vue の場合:

vue
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";

const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
    console.log('Chat event received:', e);
});

channel().listenForWhisper('typing', (e) => {
    console.log(e.name);
});
</script>

Svelte の場合:

svelte
<script>
import { useEcho } from "@laravel/echo-svelte";

const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
    console.log('Chat event received:', e);
});

channel().listenForWhisper('typing', (e) => {
    console.log(e.name);
});
</script>

通知との組み合わせ#

放送と通知を組み合わせると、ページを開き直さなくても、新しい通知を JavaScript で受け取れます。始める前に、通知の放送チャンネルのドキュメントを読んでおいてください。

通知を放送のチャンネルを使うよう設定したら、Echo の notification メソッドで聞けます。チャンネルの名前は、通知を受け取る持ち主のクラス名に合わせます。

JavaScript の場合:

js
Echo.private(`App.Models.User.${userId}`)
    .notification((notification) => {
        console.log(notification.type);
    });

React の場合:

js
import { useEchoModel } from "@laravel/echo-react";

const { channel } = useEchoModel('App.Models.User', userId);

channel().notification((notification) => {
    console.log(notification.type);
});

Vue の場合:

vue
<script setup lang="ts">
import { useEchoModel } from "@laravel/echo-vue";

const { channel } = useEchoModel('App.Models.User', userId);

channel().notification((notification) => {
    console.log(notification.type);
});
</script>

Svelte の場合:

svelte
<script>
import { useEchoModel } from "@laravel/echo-svelte";

const { channel } = useEchoModel('App.Models.User', userId);

channel().notification((notification) => {
    console.log(notification.type);
});
</script>

この例では、App\Models\User に、broadcast チャンネルで送られた通知が、すべてこの関数で受け取れます。App.Models.User.{id} のチャンネルの認可の関数は、アプリの routes/channels.php に最初から入っています。

通知を聞くのをやめる#

チャンネルからは出ずに、通知を聞くのだけやめたいときは、stopListeningForNotification を使います。

js
const callback = (notification) => {
    console.log(notification.type);
}

// Start listening...
Echo.private(`App.Models.User.${userId}`)
    .notification(callback);

// Stop listening (callback must be the same)...
Echo.private(`App.Models.User.${userId}`)
    .stopListeningForNotification(callback);

Echo のメソッド一覧#

ブラウザ側で使った、Echo の主なメソッドをまとめます。

名前 説明
Echo.channel 公開チャンネルを取り出す
Echo.private プライベートチャンネルを取り出す
Echo.join presence チャンネルに入る
Echo.encryptedPrivate 暗号化したプライベートチャンネルを取り出す
listen チャンネルで、イベントを聞く
stopListening チャンネルに残ったまま、イベントを聞くのをやめる
Echo.leaveChannel チャンネルから出る
Echo.leave チャンネルと、結びついたプライベートと presence のチャンネルから出る
Echo.socketId いまのソケット ID を返す
whisper クライアントイベントを送る
listenForWhisper クライアントイベントを聞く
notification 通知を聞く
stopListeningForNotification 通知を聞くのをやめる
here presence チャンネルに入れたとき、いる人たちを受け取る
joining presence チャンネルに人が入ってきたときに実行される
leaving presence チャンネルから人が出たときに実行される
error presence チャンネルで、確認に失敗したときなどに実行される

サーバー側のメソッドと約束ごとの一覧#

イベントやモデルに書く、サーバー側の主なものをまとめます。

名前 説明
ShouldBroadcast 付けると、イベントが放送される(キューを通る)
ShouldBroadcastNow 付けると、キューを通さず、その場で放送される(sync)
ShouldDispatchAfterCommit 付けると、トランザクションが確定したあとに放送される
ShouldRescue 付けると、放送の失敗をつかまえて記録し、アプリを止めない
InteractsWithSockets toOthers を使うイベントに必要なトレイト
InteractsWithBroadcasting broadcastVia を使うイベントに必要なトレイト
BroadcastsEvents モデルが状態の変化を自動で放送するためのトレイト
broadcastOn 放送するチャンネルを返す
broadcastAs 放送の名前を決める
broadcastWith 放送するデータを決める
broadcastQueue 放送のジョブを入れるキューの名前を決める
broadcastWhen 放送するかどうかの条件を決める
broadcastVia 放送の接続を決める(コンストラクタの中で呼ぶ)
broadcastChannel モデルのチャンネルの名前を返す
newBroadcastableEvent モデルの放送のイベントの作りを変える
toOthers いまの人以外にだけ放送する
via 放送の接続を選ぶ
Broadcast::channel チャンネルの認可の関数(またはクラス)を登録する
Broadcast::on 無名のイベントを、公開チャンネルへ放送する準備をする
Broadcast::private 無名のイベントを、プライベートチャンネルへ放送する準備をする
Broadcast::presence 無名のイベントを、presence チャンネルへ放送する準備をする
send 無名のイベントを、キューに入れて放送する
sendNow 無名のイベントを、すぐに放送する

関連するページ#

公式ドキュメント(英語)

2026年10月5日時点の内容をもとに、日本語でまとめています。

ページの一覧