
# 广播

-   [简介](#introduction)
-   [快速入门](#quickstart)
-   [服务端安装](#server-side-installation)
    -   [Reverb](#reverb)
    -   [Pusher Channels](#pusher-channels)
    -   [Ably](#ably)
-   [客户端安装](#client-side-installation)
    -   [Reverb](#client-reverb)
    -   [Pusher Channels](#client-pusher-channels)
    -   [Ably](#client-ably)
-   [概念概览](#concept-overview)
    -   [使用示例应用](#using-example-application)
-   [定义广播事件](#defining-broadcast-events)
    -   [广播名称](#broadcast-name)
    -   [广播数据](#broadcast-data)
    -   [广播队列](#broadcast-queue)
    -   [广播条件](#broadcast-conditions)
    -   [广播与数据库事务](#broadcasting-and-database-transactions)
-   [授权频道](#authorizing-channels)
    -   [定义授权回调](#defining-authorization-callbacks)
    -   [定义频道类](#defining-channel-classes)
-   [广播事件](#broadcasting-events)
    -   [仅广播给其他客户端](#only-to-others)
    -   [自定义连接](#customizing-the-connection)
    -   [匿名事件](#anonymous-events)
    -   [广播异常处理](#rescuing-broadcasts)
-   [接收广播](#receiving-broadcasts)
    -   [监听事件](#listening-for-events)
    -   [离开频道](#leaving-a-channel)
    -   [命名空间](#namespaces)
    -   [使用 React、Vue 或 Svelte](#using-react-or-vue)
-   [Presence 频道](#presence-channels)
    -   [授权 Presence 频道](#authorizing-presence-channels)
    -   [加入 Presence 频道](#joining-presence-channels)
    -   [向 Presence 频道广播](#broadcasting-to-presence-channels)
-   [模型广播](#model-broadcasting)
    -   [模型广播约定](#model-broadcasting-conventions)
    -   [监听模型广播](#listening-for-model-broadcasts)
-   [客户端事件](#client-events)
-   [通知](#notifications)

## 简介

在许多现代 Web 应用中，WebSocket 被用来实现实时、动态更新的用户界面。当服务器上的某些数据发生更新时，通常会通过 WebSocket 连接发送一条消息，并由客户端进行处理。相比持续轮询应用服务器来检查需要反映到 UI 中的数据变化，WebSocket 提供了一种更加高效的替代方案。
例如，假设你的应用可以将用户数据导出为 CSV 文件，并通过电子邮件发送给用户。但是，生成这个 CSV 文件需要几分钟时间，因此你决定通过一个 [队列任务](/docs/laravel/13.x/queues) 来生成并发送 CSV。当 CSV 文件生成完成并发送给用户后，我们可以使用事件广播来触发一个 `App\Events\UserDataExported` 事件，并由应用中的 JavaScript 接收该事件。一旦接收到这个事件，我们就可以向用户显示一条消息，告知他们 CSV 文件已经通过电子邮件发送，而不需要用户手动刷新页面。



为了帮助你构建这类功能，Laravel 可以轻松地通过 WebSocket 连接“广播”服务端的 Laravel [事件](/docs/laravel/13.x/events)。通过广播 Laravel 事件，你可以在服务端 Laravel 应用和客户端 JavaScript 应用之间共享相同的事件名称和数据。

广播背后的核心概念很简单：客户端在前端连接到指定名称的频道，而 Laravel 应用则在后端向这些频道广播事件。这些事件可以包含任何你希望提供给前端使用的附加数据。

#### 支持的驱动

默认情况下，Laravel 提供了三种服务端广播驱动供你选择：[Laravel Reverb](https://reverb.laravel.com)、[Pusher Channels](https://pusher.com/channels) 和 [Ably](https://ably.com)。

> [!注意]
> 在深入了解事件广播之前，请确保你已经阅读过 Laravel 关于 [事件和监听器](/docs/laravel/13.x/events) 的文档。

## 快速开始

默认情况下，新建的 Laravel 应用并不会启用广播功能。你可以使用 `install:broadcasting` Artisan 命令来启用广播：

```shell
php artisan install:broadcasting
```

`install:broadcasting` 命令会提示你选择要使用的事件广播服务。此外，它还会创建 `config/broadcasting.php` 配置文件以及 `routes/channels.php` 文件，你可以在其中注册应用的广播授权路由和回调。

Laravel 开箱即用地支持多种广播驱动：[Laravel Reverb](/docs/laravel/13.x/reverb)、[Pusher Channels](https://pusher.com/channels)、[Ably](https://ably.com)，以及用于本地开发和调试的 `log` 驱动。

此外，Laravel 还提供了一个 `null` 驱动，可以让你在测试期间禁用广播。`config/broadcasting.php` 配置文件中包含了这些驱动各自的配置示例。




应用中所有与事件广播相关的配置都存储在 `config/broadcasting.php` 配置文件中。如果你的应用中还不存在这个文件，也不用担心；当你运行 `install:broadcasting` Artisan 命令时，它会自动创建。

#### 下一步

启用事件广播后，你就可以继续了解如何 [定义广播事件](#defining-broadcast-events) 和 [监听事件](#listening-for-events)。
如果你正在使用 Laravel 的 React、Vue 或 Svelte [入门套件](/docs/laravel/13.x/starter-kits)，你可以通过 Echo 提供的 [useEcho Hook](#using-react-or-vue) 来监听事件。
> [!注意]
> 在广播任何事件之前，你应该先配置并运行一个 [队列 Worker](/docs/laravel/13.x/queues)。所有事件广播都会通过队列任务执行，这样可以避免事件广播对应用的响应时间造成明显影响。

## 服务端安装

要开始使用 Laravel 的事件广播功能，我们需要先在 Laravel 应用中完成一些配置，并安装几个相关的软件包。
事件广播是通过服务端的广播驱动实现的。该驱动会广播 Laravel 事件，以便 Laravel Echo（一个 JavaScript 库）能够在浏览器客户端中接收这些事件。不用担心，下面我们会一步一步介绍整个安装过程。

### Reverb

如果你希望使用 Reverb 作为事件广播服务，可以通过执行带有 `--reverb` 选项的 `install:broadcasting` Artisan 命令，快速启用 Laravel 的广播功能。
这个 Artisan 命令会安装 Reverb 所需的 Composer 和 NPM 软件包，并在应用的 `.env` 文件中添加或更新相应的环境变量：

```shell
php artisan install:broadcasting --reverb
```



#### 手动安装

运行 `install:broadcasting` 命令时，系统会提示你安装 [Laravel Reverb](/docs/laravel/13.x/reverb)。当然，你也可以使用 Composer 包管理器手动安装 Reverb：

```shell
composer require laravel/reverb
```

软件包安装完成后，你可以运行 Reverb 的安装命令，用于发布配置文件、添加 Reverb 所需的环境变量，并在应用中启用事件广播：

```shell
php artisan reverb:install
```

你可以在 [Reverb 文档](/docs/laravel/13.x/reverb) 中查看详细的安装和使用说明。

### Pusher Channels

如果你希望使用 Pusher 作为事件广播服务，可以通过执行带有 `--pusher` 选项的 `install:broadcasting` Artisan 命令，快速启用 Laravel 的广播功能。

这个 Artisan 命令会提示你输入 Pusher 凭据，安装 Pusher 的 PHP 和 JavaScript SDK，并在应用的 `.env` 文件中添加或更新相应的环境变量：

```shell
php artisan install:broadcasting --pusher
```

#### 手动安装

如果要手动安装 Pusher 支持，你需要使用 Composer 包管理器安装 Pusher Channels PHP SDK：

```shell
composer require pusher/pusher-php-server
```

接下来，你需要在 `config/broadcasting.php` 配置文件中配置 Pusher Channels 的凭据。
该文件中已经包含了一个 Pusher Channels 的配置示例，你可以快速配置你的 key、secret 和 application ID。
通常情况下，你应该在应用的 `.env` 文件中配置 Pusher Channels 凭据：

```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` 配置还允许你指定 Channels 支持的其他 `options`，例如 cluster。

然后，在应用的 `.env` 文件中，将 `BROADCAST_CONNECTION` 环境变量设置为 `pusher`：

```ini
BROADCAST_CONNECTION=pusher
```

最后，你就可以安装并配置 [Laravel Echo](#client-side-installation) 了，它将负责在客户端接收广播事件。

### Ably

> [!NOTE]
> 下面的文档介绍的是如何以“Pusher 兼容模式”使用 Ably。不过，Ably 团队推荐并维护了专门的广播驱动和 Echo 客户端，可以充分利用 Ably 提供的独特功能。有关如何使用 Ably 官方维护驱动的更多信息，请参阅 [Ably 的 Laravel broadcaster 文档](https://github.com/ably/laravel-broadcaster)。

如果你希望使用 [Ably](https://ably.com) 作为事件广播服务，可以通过执行带有 `--ably` 选项的 `install:broadcasting` Artisan 命令，快速启用 Laravel 的广播功能。

这个 Artisan 命令会提示你输入 Ably 凭据，安装 Ably 的 PHP 和 JavaScript SDK，并在应用的 `.env` 文件中添加或更新相应的环境变量：

```shell
php artisan install:broadcasting --ably
```

**继续之前，你需要在 Ably 应用设置中启用 Pusher 协议支持。你可以在 Ably 应用设置面板中的 “Protocol Adapter Settings” 部分启用该功能。**

#### 手动安装

如果要手动安装 Ably 支持，你需要使用 Composer 包管理器安装 Ably PHP SDK：

```shell
composer require ably/ably-php
```

接下来，你需要在 `config/broadcasting.php` 配置文件中配置 Ably 凭据。

该文件中已经包含了一个 Ably 配置示例，你可以快速设置你的 key。通常，这个值应该通过 `ABLY_KEY` [环境变量](/docs/laravel/13.x/configuration#environment-configuration) 进行设置：

```ini
ABLY_KEY=your-ably-key
```



然后，在应用的 `.env` 文件中，将 `BROADCAST_CONNECTION` 环境变量设置为 `ably`：

```ini
BROADCAST_CONNECTION=ably
```

最后，你就可以安装并配置 [Laravel Echo](#client-side-installation) 了，它将负责在客户端接收广播事件。

## 客户端安装

### Reverb

[Laravel Echo](https://github.com/laravel/echo) 是一个 JavaScript 库，可以让你非常方便地订阅频道，并监听由服务端广播驱动发送的事件。

当你通过 `install:broadcasting` Artisan 命令安装 Laravel Reverb 时，Reverb 和 Echo 所需的脚手架代码及配置会自动添加到你的应用中。

不过，如果你希望手动配置 Laravel Echo，也可以按照下面的说明进行操作。

#### 手动安装

如果要为应用前端手动配置 Laravel Echo，首先需要安装 `pusher-js` 包，因为 Reverb 使用 Pusher 协议来处理 WebSocket 订阅、频道和消息：

```shell
npm install --save-dev laravel-echo pusher-js
```

安装 Echo 后，就可以在应用的 JavaScript 中创建一个新的 Echo 实例。
一个比较合适的位置是在 Laravel 框架默认提供的 `resources/js/app.js` 文件底部：

```js tab=JavaScript
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'],
});
```

```js tab=React
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'],
});
```

```js tab=Vue
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'],
});
```

```js tab=Svelte
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'],
});
```



接下来，你应该编译应用的前端资源：

```shell
npm run build
```

> [!警告]
> Laravel Echo 的 `reverb` 广播器要求使用 laravel-echo v1.16.0 或更高版本。

### Pusher Channels

[Laravel Echo](https://github.com/laravel/echo) 是一个 JavaScript 库，可以让你轻松订阅频道，并监听由服务端广播驱动发送的事件。

当你通过 `install:broadcasting --pusher` Artisan 命令安装广播支持时，Pusher 和 Echo 所需的脚手架代码及配置会自动添加到你的应用中。

不过，如果你希望手动配置 Laravel Echo，也可以按照下面的说明进行操作。

#### 手动安装

如果要为应用前端手动配置 Laravel Echo，首先需要安装 `laravel-echo` 和 `pusher-js` 软件包。它们使用 Pusher 协议来处理 WebSocket 订阅、频道和消息：


```shell
npm install --save-dev laravel-echo pusher-js
```

安装 Echo 后，就可以在应用的 `resources/js/app.js` 文件中创建一个新的 Echo 实例：

```js tab=JavaScript
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
});
```

```js tab=React
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"],
});
```

```js tab=Vue
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"],
});
```

```js tab=Svelte
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 环境变量设置正确的值。如果这些变量还不存在于 `.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"

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 配置后，就可以编译应用的前端资源：

```shell
npm run build
```

> [!注意]
> 如果你想进一步了解如何编译应用的 JavaScript 资源，请参阅 [Vite](/docs/laravel/13.x/vite) 文档。

#### 使用现有的客户端实例

如果你已经有一个预先配置好的 Pusher Channels 客户端实例，并希望让 Echo 直接使用它，可以通过 `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

> [!注意]
> 下面的文档介绍的是如何以“Pusher 兼容模式”使用 Ably。不过，Ably 团队推荐并维护了一套专门的广播驱动和 Echo 客户端，可以充分利用 Ably 提供的独特功能。有关如何使用 Ably 官方维护驱动的更多信息，请参阅 [Ably 的 Laravel broadcaster 文档](https://github.com/ably/laravel-broadcaster)。



[Laravel Echo](https://github.com/laravel/echo) 是一个 JavaScript 库，可以让你轻松订阅频道，并监听由服务端广播驱动发送的事件。

当你通过 `install:broadcasting --ably` Artisan 命令安装广播支持时，Ably 和 Echo 所需的脚手架代码及配置会自动添加到你的应用中。

不过，如果你希望手动配置 Laravel Echo，也可以按照下面的说明进行操作。

#### 手动安装

如果要为应用前端手动配置 Laravel Echo，首先需要安装 `laravel-echo` 和 `pusher-js` 软件包。它们使用 Pusher 协议来处理 WebSocket 订阅、频道和消息：

```shell
npm install --save-dev laravel-echo pusher-js
```

**继续之前，你需要在 Ably 应用设置中启用 Pusher 协议支持。你可以在 Ably 应用设置面板中的 “Protocol Adapter Settings” 部分启用该功能。**

安装 Echo 后，就可以在应用的 `resources/js/app.js` 文件中创建一个新的 Echo 实例：

```js tab=JavaScript
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,
});
```

```js tab=React
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,
});
```

```js tab=Vue
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,
});
```

```js tab=Svelte
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,
});
```



你可能已经注意到，我们的 Ably Echo 配置中引用了一个 `VITE_ABLY_PUBLIC_KEY` 环境变量。这个变量的值应该设置为你的 Ably 公钥。你的公钥就是 Ably key 中位于 `:` 字符之前的那一部分。

根据你的需要调整好 Echo 配置后，就可以编译应用的前端资源：

```shell
npm run dev
```

> [!注意]
> 如果你想进一步了解如何编译应用的 JavaScript 资源，请参阅 [Vite](/docs/laravel/13.x/vite) 文档。

## 概念概览

Laravel 的事件广播允许你通过基于驱动的 WebSocket 方式，将服务端 Laravel 事件广播到客户端 JavaScript 应用。

目前，Laravel 内置支持 [Laravel Reverb](https://reverb.laravel.com)、[Pusher Channels](https://pusher.com/channels) 和 [Ably](https://ably.com) 驱动。这些事件可以通过客户端的 [Laravel Echo](#client-side-installation) JavaScript 包轻松接收和处理。

事件是通过“频道（channels）”进行广播的，这些频道可以设置为公共频道或私有频道。

任何访问你应用的用户都可以订阅公共频道，而不需要进行任何身份认证或授权；但是，如果要订阅私有频道，用户必须先通过身份认证，并且获得监听该频道的授权。

### 使用示例应用

在深入了解事件广播的各个组成部分之前，我们先以一个电商应用为例，从整体上了解一下事件广播的工作方式。

假设我们的应用中有一个页面，允许用户查看自己订单的配送状态。

同时假设，当应用处理订单配送状态更新时，会触发一个 `OrderShipmentStatusUpdated` 事件：

```php
use App\Events\OrderShipmentStatusUpdated;

OrderShipmentStatusUpdated::dispatch($order);
```



#### `ShouldBroadcast` 接口

当用户正在查看自己的某个订单时，我们不希望他们为了查看状态更新而手动刷新页面。相反，我们希望这些更新在产生时就自动广播到应用中。

因此，我们需要让 `OrderShipmentStatusUpdated` 事件实现 `ShouldBroadcast` 接口。这样，当事件被触发时，Laravel 就会自动对其进行广播：

```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
{
    /**
     * 订单实例。
     *
     * @var \App\Models\Order
     */
    public $order;
}
```

`ShouldBroadcast` 接口要求事件必须定义一个 `broadcastOn` 方法。这个方法负责返回该事件应该广播到哪些频道。在通过 Laravel 生成的事件类中，通常已经包含了这个方法的空实现，因此我们只需要补充具体逻辑即可。由于我们只希望订单的创建者能够查看订单状态更新，因此这里会将事件广播到一个与该订单绑定的私有频道：

```php
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PrivateChannel;

/**
 * 获取事件应该广播到的频道。
 */
public function broadcastOn(): Channel
{
    return new PrivateChannel('orders.'.$this->order->id);
}
```

如果你希望事件同时广播到多个频道，也可以返回一个 `array`：

```php
use Illuminate\Broadcasting\PrivateChannel;

/**
 * 获取事件应该广播到的频道。
 *
 * @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}` 占位符来表示频道名称中的 “ID” 部分是一个通配符。

#### 监听事件广播

接下来，我们只需要在 JavaScript 应用中监听这个事件即可。
我们可以使用 [Laravel Echo](#client-side-installation) 来完成。Laravel Echo 内置的 React、Vue 和 Svelte Hooks 可以让你非常方便地开始监听事件。默认情况下，事件中的所有公共属性都会包含在广播事件中：


```js tab=React
import { useEcho } from "@laravel/echo-react";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
```

```vue tab=Vue
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
</script>
```

```svelte tab=Svelte
<script>
import { useEcho } from "@laravel/echo-svelte";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
</script>
```



## 定义广播事件

要告诉 Laravel 某个事件需要被广播，你必须让该事件类实现 `Illuminate\Contracts\Broadcasting\ShouldBroadcast` 接口。
Laravel 框架生成的所有事件类中都已经导入了这个接口，因此你可以很方便地将它添加到任何事件中。`ShouldBroadcast` 接口要求你实现一个方法：`broadcastOn`。`broadcastOn` 方法应该返回一个频道，或者返回一个频道数组，用来指定该事件应该广播到哪些频道。这些频道应该是 `Channel`、`PrivateChannel` 或 `PresenceChannel` 的实例。`Channel` 实例表示公共频道，任何用户都可以订阅；而 `PrivateChannel` 和 `PresenceChannel` 表示私有频道，需要进行 [频道授权](#authorizing-channels)：

```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;

    /**
     * 创建一个新的事件实例。
     */
    public function __construct(
        public User $user,
    ) {}

    /**
     * 获取事件应该广播到的频道。
     *
     * @return array<int, \Illuminate\Broadcasting\Channel>
     */
    public function broadcastOn(): array
    {
        return [
            new PrivateChannel('user.'.$this->user->id),
        ];
    }
}
```

实现 `ShouldBroadcast` 接口之后，你只需要像平时一样 [触发事件](/docs/laravel/13.x/events) 即可。事件触发后，Laravel 会自动创建一个 [队列任务](/docs/laravel/13.x/queues)，并使用你配置的广播驱动来广播该事件。

### 广播名称

默认情况下，Laravel 会使用事件的类名作为广播事件名称。不过，你可以通过在事件类中定义 `broadcastAs` 方法来自定义广播名称：

```php
/**
 * 事件的广播名称。
 */
public function broadcastAs(): string
{
    return 'server.created';
}
```



如果你使用 `broadcastAs` 方法自定义了广播名称，那么在注册监听器时，需要确保事件名称前面带有一个 `.` 字符。这样可以告诉 Echo 不要在事件名称前自动添加应用的命名空间：

```javascript
.listen('.server.created', function (e) {
    // ...
});
```

### 广播数据

当一个事件被广播时，它所有的 `public` 属性都会被自动序列化，并作为事件的 payload 进行广播。因此，你可以在 JavaScript 应用中访问这些公开数据。例如，如果你的事件中只有一个公开的 `$user` 属性，并且该属性包含一个 Eloquent 模型，那么事件广播的 payload 大致如下：


```json
{
    "user": {
        "id": 1,
        "name": "Patrick Stewart"
        ...
    }
}
```

不过，如果你希望更精细地控制广播的 payload，可以在事件类中添加一个 `broadcastWith` 方法。
这个方法应该返回一个数组，其中包含你希望作为事件 payload 广播的数据：

```php
/**
 * 获取需要广播的数据。
 *
 * @return array<string, mixed>
 */
public function broadcastWith(): array
{
    return ['id' => $this->user->id];
}
```

### 广播队列

默认情况下，每个广播事件都会被放入 `queue.php` 配置文件中所指定的默认队列连接和默认队列。
你可以在事件类中使用 `Connection` 和 `Queue` 属性，自定义广播器使用的队列连接和队列名称：

```php
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Queue;

#[Connection('redis')]
#[Queue('default')]
class ServerCreated implements ShouldBroadcast
{
    // ...
}
```

或者，你也可以通过在事件类中定义 `broadcastQueue` 方法来自定义队列名称：

```php
/**
 * 用于放置广播任务的队列名称。
 */
public function broadcastQueue(): string
{
    return 'default';
}
```



如果你希望使用 `sync` 队列来广播事件，而不是使用默认的队列驱动，可以实现 `ShouldBroadcastNow` 接口，而不是 `ShouldBroadcast`：

```php
<?php

namespace App\Events;

use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;

class OrderShipmentStatusUpdated implements ShouldBroadcastNow
{
    // ...
}
```

### 广播条件

有时，你可能只希望在某个条件成立时才广播事件。你可以通过在事件类中添加 `broadcastWhen` 方法来定义这个条件：

```php
/**
 * 判断该事件是否应该广播。
 */
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;
}
```

> [!注意]
> 如果你想进一步了解如何处理这类问题，请参阅有关 [队列任务与数据库事务](/docs/laravel/13.x/queues#jobs-and-database-transactions) 的文档。


## 授权频道

私有频道要求你授权当前已通过身份验证的用户确实可以监听该频道。实现这一点的方法是向你的 Laravel 应用程序发送一个包含频道名称的 HTTP 请求，并由应用程序判断用户是否可以监听该频道。使用 Laravel Echo 时，用于授权订阅私有频道的 HTTP 请求将自动发送。

安装广播功能后，Laravel 会尝试自动注册 `/broadcasting/auth` 路由来处理授权请求。如果 Laravel 无法自动注册这些路由，你可以在应用程序的 `/bootstrap/app.php` 文件中手动注册：

```php
->withRouting(
    web: __DIR__.'/../routes/web.php',
    channels: __DIR__.'/../routes/channels.php',
    health: '/up',
)
```

### 定义授权回调

接下来，我们需要定义实际判断当前已通过身份验证的用户是否可以监听指定频道的逻辑。这需要在 `install:broadcasting` Artisan 命令创建的 `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}` 占位符来表示频道名称中的“ID”部分是一个通配符。



你可以使用 `channel:list` Artisan 命令查看应用程序的广播授权回调列表：

```shell
php artisan channel:list
```

#### 授权回调模型绑定

与 HTTP 路由一样，频道路由也可以利用隐式和显式的路由模型绑定。例如，你可以请求一个实际的 `Order` 模型实例，而不是接收字符串或数字形式的订单 ID：

```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 路由模型绑定不同，频道模型绑定不支持自动[隐式模型绑定作用域](/docs/laravel/13.x/routing#implicit-model-binding-scoping)。不过，这很少会成为问题，因为大多数频道都可以根据单个模型的唯一主键来限定作用域。

#### 授权回调身份验证

私有广播频道和在线广播频道通过应用程序的默认身份验证守卫对当前用户进行身份验证。如果用户未通过身份验证，频道授权将自动被拒绝，并且不会执行授权回调。不过，如果有需要，你可以指定多个自定义守卫，用于对传入请求进行身份验证：

```php
Broadcast::channel('channel', function () {
    // ...
}, ['guards' => ['web', 'admin']]);
```

### 定义频道类

如果应用程序使用了许多不同的频道，`routes/channels.php` 文件可能会变得臃肿。因此，你可以使用频道类来代替闭包授权频道。要生成频道类，请使用 `make:channel` Artisan 命令。该命令会在 `App/Broadcasting` 目录中创建一个新的频道类。

```shell
php artisan make:channel OrderChannel
```



接下来，在你的 `routes/channels.php` 文件中注册频道：

```php
use App\Broadcasting\OrderChannel;

Broadcast::channel('orders.{order}', OrderChannel::class);
```

最后，你可以将频道的授权逻辑放在频道类的 `join` 方法中。此 `join` 方法将包含通常放在频道授权闭包中的相同逻辑。你也可以利用频道模型绑定：

```php
<?php

namespace App\Broadcasting;

use App\Models\Order;
use App\Models\User;

class OrderChannel
{
    /**
     * 创建一个新的频道实例。
     */
    public function __construct() {}

    /**
     * 验证用户对该频道的访问权限。
     */
    public function join(User $user, Order $order): array|bool
    {
        return $user->id === $order->user_id;
    }
}
```

> [!注意]
> 与 Laravel 中的许多其他类一样，通道类会由[服务容器](/docs/laravel/13.x/container)自动解析。因此，你可以在通道类的构造函数中通过类型提示声明该通道所需的任何依赖项。


## 广播事件

定义事件并为其实现 `ShouldBroadcast` 接口后，你只需使用该事件的 `dispatch` 方法触发事件即可。事件分发器会检测到该事件标记了 `ShouldBroadcast` 接口，并将该事件加入队列以进行广播：

```php
use App\Events\OrderShipmentStatusUpdated;

OrderShipmentStatusUpdated::dispatch($order);
```

### 仅广播给其他人

在构建使用事件广播的应用程序时，你有时可能需要将事件广播给指定频道的所有订阅者，但不包括当前用户。你可以使用 `broadcast` 辅助函数和 `toOthers` 方法来实现这一点：

```php
use App\Events\OrderShipmentStatusUpdated;

broadcast(new OrderShipmentStatusUpdated($update))->toOthers();
```



为了更好地理解何时应该使用 `toOthers` 方法，我们来设想一个任务列表应用：用户可以通过输入任务名称来创建新任务。

为了创建任务，你的应用可能会向 `/task` URL 发送请求，该请求会广播任务创建事件，并返回新任务的 JSON 表示形式。当你的 JavaScript 应用收到该接口的响应时，可以直接将新任务添加到任务列表中，如下所示：

```js
axios.post('/task', task)
    .then((response) => {
        this.tasks.push(response.data);
    });
```

但是，请记住，我们也会广播任务创建事件。如果你的 JavaScript 应用同时监听了此事件，并在收到事件时将任务添加到任务列表中，那么列表中就会出现重复的任务：一个来自接口的响应，另一个来自广播事件。

你可以通过使用 `toOthers` 方法来解决这个问题，该方法会指示广播系统不要将此事件广播给当前用户。

> [!警告]
> 你的事件类必须使用 `Illuminate\Broadcasting\InteractsWithSockets` Trait，才能调用 `toOthers` 方法。

#### 配置

当你初始化 Laravel Echo 实例时，系统会为该连接分配一个 Socket ID。

如果你在 JavaScript 应用中使用全局的 [Axios](https://github.com/axios/axios) 实例来发送 HTTP 请求，那么每个发出的请求都会自动附带一个 `X-Socket-ID` 请求头，其中包含该 Socket ID。

随后，当你调用 `toOthers` 方法时，Laravel 会从请求头中提取 Socket ID，并指示广播系统不要向具有该 Socket ID 的任何连接广播此事件。



如果你没有使用全局 Axios 实例，则需要手动配置 JavaScript 应用程序，使其在所有发出的请求中都携带 `X-Socket-ID` 请求头。你可以使用 `Echo.socketId` 方法获取 Socket ID：

```js
var socketId = Echo.socketId();
```

### 自定义连接

如果你的应用程序需要与多个广播连接进行交互，并且你希望使用默认连接以外的广播驱动程序来广播事件，可以使用 `via` 方法指定要向哪个连接推送事件：

```php
use App\Events\OrderShipmentStatusUpdated;

broadcast(new OrderShipmentStatusUpdated($update))->via('pusher');
```

或者，你也可以在事件的构造函数中调用 `broadcastVia` 方法，指定该事件使用的广播连接。不过，在执行此操作之前，你应确保事件类使用了 `InteractsWithBroadcasting` Trait：

```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;

    /**
     * 创建一个新的事件实例。
     */
    public function __construct()
    {
        $this->broadcastVia('pusher');
    }
}
```

### 匿名事件

有时，你可能希望向应用程序的前端广播一个简单的事件，而不必专门创建一个事件类。为此，`Broadcast` Facade 允许你广播匿名事件：

```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"
}
```

如果你希望在私有频道（Private Channel）或在线状态频道（Presence Channel）上广播匿名事件，可以使用 `private` 和 `presence` 方法：

```php
Broadcast::private('orders.'.$order->id)->send();
Broadcast::presence('channels.'.$channel->id)->send();
```

使用 `send` 方法广播匿名事件时，该事件会被分发到应用程序的[队列](https://laravel.com/docs/13.x/queues?utm_source=chatgpt.com)中进行处理。

但是，如果你希望立即广播事件，可以使用 `sendNow` 方法：

```php
Broadcast::on('orders.'.$order->id)->sendNow();
```

如果你希望将事件广播给频道的所有订阅者，但排除当前已认证的用户，可以调用 `toOthers` 方法：

```php
Broadcast::on('orders.'.$order->id)
    ->toOthers()
    ->send();
```

### 广播异常兜底处理

当应用程序的队列服务器不可用，或者 Laravel 在广播事件时遇到错误，就会抛出异常，通常会导致最终用户看到应用程序错误页面。

由于事件广播通常只是应用程序核心功能的辅助功能，因此，你可以在事件类中实现 `ShouldRescue` 接口，避免这些异常影响用户体验。

实现了 `ShouldRescue` 接口的事件，在尝试广播时会自动使用 Laravel 的 [rescue 辅助函数](https://laravel.com/docs/13.x/helpers?utm_source=chatgpt.com#method-rescue)。

该辅助函数会捕获所有异常，将异常报告给应用程序的异常处理器以便记录日志，同时允许应用程序继续正常执行，而不会中断用户当前的操作流程：

```php
<?php

namespace App\Events;

use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Broadcasting\ShouldRescue;

class ServerCreated implements ShouldBroadcast, ShouldRescue
{
    // ...
}
```



## 接收广播

### 监听事件

完成 [Laravel Echo 的安装与实例化](#client-side-installation) 后，就可以开始监听 Laravel 应用程序广播的事件了。首先，使用 `channel` 方法获取一个频道实例，然后调用 `listen` 方法监听指定的事件：

```js
Echo.channel(`orders.${this.order.id}`)
    .listen('OrderShipmentStatusUpdated', (e) => {
        console.log(e.order.name);
    });
```

如果你想监听私有频道上的事件，请改用 `private` 方法。你可以继续链式调用 `listen` 方法，以便在同一个频道上监听多个事件：

```js
Echo.private(`orders.${this.order.id}`)
    .listen(/* ... */)
    .listen(/* ... */)
    .listen(/* ... */);
```

#### 停止监听事件

如果你想停止监听某个事件，但不 [离开频道](#leaving-a-channel)，可以使用 `stopListening` 方法：

```js
Echo.private(`orders.${this.order.id}`)
    .stopListening('OrderShipmentStatusUpdated');
```

### 离开频道

要离开某个频道，可以在 Echo 实例上调用 `leaveChannel` 方法：

```js
Echo.leaveChannel(`orders.${this.order.id}`);
```

如果你想离开某个频道，同时退出与其关联的私有频道和在线状态频道，可以调用 `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 的 Hooks，让监听事件变得更加简单。
首先，调用 `useEcho` Hook 来监听私有事件。当使用该 Hook 的组件被卸载时，`useEcho` 会自动退出相应的频道。

```js tab=React
import { useEcho } from "@laravel/echo-react";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
```

```vue tab=Vue
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
</script>
```

```svelte tab=Svelte
<script>
import { useEcho } from "@laravel/echo-svelte";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
</script>
```

你也可以向 `useEcho` 传入一个事件数组，从而同时监听多个事件：

```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` Hook 的组件被卸载时，该 Hook 会自动退出相应的频道。
不过，在某些情况下，你可能需要通过编程方式手动控制频道监听的启停。此时，可以使用 `useEcho` 返回的函数，根据需要手动停止或重新开始监听。

```js tab=React
import { useEcho } from "@laravel/echo-react";

const { leaveChannel, leave, stopListening, listen } = useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);

// 停止监听，但不退出频道……
stopListening();

// 重新开始监听……
listen();

// 退出频道……
leaveChannel();

// 退出频道，同时退出其关联的私有频道和在线状态频道……
leave();
```

```vue tab=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);
    },
);

// 停止监听，但不退出频道……
stopListening();

// 重新开始监听……
listen();

// 退出频道……
leaveChannel();

// 退出频道，同时退出其关联的私有频道和在线状态频道……
leave();
</script>
```

```svelte tab=Svelte
<script>
import { useEcho } from "@laravel/echo-svelte";

const { leaveChannel, leave, stopListening, listen } = useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);

// 停止监听，但不退出频道……
stopListening();

// 重新开始监听……
listen();

// 退出频道……
leaveChannel();

// 退出频道，同时退出其关联的私有频道和在线状态频道……
leave();
</script>
```




#### 连接到公共频道

要连接到公共频道，可以使用 `useEchoPublic` Hook：

```js tab=React
import { useEchoPublic } from "@laravel/echo-react";

useEchoPublic("posts", "PostPublished", (e) => {
    console.log(e.post);
});
```

```vue tab=Vue
<script setup lang="ts">
import { useEchoPublic } from "@laravel/echo-vue";

useEchoPublic("posts", "PostPublished", (e) => {
    console.log(e.post);
});
</script>
```

```svelte tab=Svelte
<script>
import { useEchoPublic } from "@laravel/echo-svelte";

useEchoPublic("posts", "PostPublished", (e) => {
    console.log(e.post);
});
</script>
```

#### 连接到在线状态频道

要连接到在线状态频道，可以使用 `useEchoPresence` Hook：

```js tab=React
import { useEchoPresence } from "@laravel/echo-react";

useEchoPresence("posts", "PostPublished", (e) => {
    console.log(e.post);
});
```

```vue tab=Vue
<script setup lang="ts">
import { useEchoPresence } from "@laravel/echo-vue";

useEchoPresence("posts", "PostPublished", (e) => {
    console.log(e.post);
});
</script>
```

```svelte tab=Svelte
<script>
import { useEchoPresence } from "@laravel/echo-svelte";

useEchoPresence("posts", "PostPublished", (e) => {
    console.log(e.post);
});
</script>
```

#### 连接状态

你可以使用 `useConnectionStatus` Hook 获取当前 WebSocket 连接状态。该 Hook 提供响应式状态，当连接状态发生变化时会自动更新：

```js tab=React
import { useConnectionStatus } from "@laravel/echo-react";

function ConnectionIndicator() {
    const status = useConnectionStatus();

    return <div>Connection: {status}</div>;
}
```

```vue tab=Vue
<script setup lang="ts">
import { useConnectionStatus } from "@laravel/echo-vue";

const status = useConnectionStatus();
</script>

<template>
    <div>Connection: {{ status }}</div>
</template>
```

```svelte tab=Svelte
<script>
import { useConnectionStatus } from "@laravel/echo-svelte";

const status = useConnectionStatus();
</script>

<div>Connection: {status()}</div>
```

可能的状态值包括：

<div class="content-list" markdown="1">

- `connected` - 已成功连接到 WebSocket 服务器。
- `connecting` - 正在进行初始连接尝试。
- `reconnecting` - 断开连接后正在尝试重新连接。
- `disconnected` - 未连接，且未尝试重新连接。
- `failed` - 连接失败，且不会重试。

</div>



## 在线状态频道

在线状态频道（Presence Channels）在私有频道的安全性基础上，提供了额外的功能，使用户能够了解哪些人订阅了该频道。这使得构建强大的协作应用程序功能变得更加容易，例如，当其他用户正在查看同一页面时通知用户，或者列出聊天室中的成员。

### 授权在线状态频道

所有在线状态频道也都是私有频道；因此，用户必须[获得访问授权](#authorizing-channels)。但是，在为在线状态频道定义授权回调时，如果用户被授权加入频道，你不应该返回 `true`。相反，你应该返回一个包含用户相关数据的数组。

授权回调返回的数据将提供给 JavaScript 应用程序中的在线状态频道事件监听器。如果用户未被授权加入在线状态频道，你应该返回 `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];
    }
});
```

### 加入在线状态频道

要加入在线状态频道，可以使用 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` 方法将被执行。当身份验证端点返回的 HTTP 状态码不是 200，或者解析返回的 JSON 时出现问题，`error` 方法将被执行。



### 向在线状态频道广播

在线状态频道可以像公共频道或私有频道一样接收事件。以聊天室为例，我们可能希望向聊天室的在线状态频道广播 `NewMessage` 事件。为此，我们需要从事件的 `broadcastOn` 方法中返回一个 `PresenceChannel` 实例：

```php
/**
 * 获取事件应该广播到的频道。
 *
 * @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();
```

与其他类型的事件一样，你可以使用 Echo 的 `listen` 方法监听发送到在线状态频道的事件：

```js
Echo.join(`chat.${roomId}`)
    .here(/* ... */)
    .joining(/* ... */)
    .leaving(/* ... */)
    .listen('NewMessage', (e) => {
        // ...
    });
```

## 模型广播

> [!警告]
> 在阅读以下关于模型广播的文档之前，我们建议你先熟悉 Laravel 模型广播服务的一般概念，以及如何手动创建和监听广播事件。

当应用程序的 [Eloquent 模型](/docs/laravel/13.x/eloquent)被创建、更新或删除时，广播事件是一种常见的做法。当然，你可以通过手动[为 Eloquent 模型状态变化定义自定义事件](/docs/laravel/13.x/eloquent#events)，并让这些事件实现 `ShouldBroadcast` 接口，轻松实现这一功能。

但是，如果你没有在应用程序中将这些事件用于其他目的，那么仅仅为了广播事件而创建事件类可能会比较繁琐。为了解决这个问题，Laravel 允许你指定 Eloquent 模型在状态发生变化时自动进行广播。



首先，你的 Eloquent 模型应该使用 `Illuminate\Database\Eloquent\BroadcastsEvents` trait。此外，模型还应该定义一个 `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;

    /**
     * 获取文章所属的用户。
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    /**
     * 获取模型事件应该广播到的频道。
     *
     * @return array<int, \Illuminate\Broadcasting\Channel|\Illuminate\Database\Eloquent\Model>
     */
    public function broadcastOn(string $event): array
    {
        return [$this, $this->user];
    }
}
```

一旦你的模型使用了此 trait 并定义了广播频道，当模型实例被创建、更新、删除、移入回收站或恢复时，它就会开始自动广播事件。

此外，你可能已经注意到，`broadcastOn` 方法接收一个字符串类型的 `$event` 参数。此参数包含模型上发生的事件类型，其值可能是 `created`、`updated`、`deleted`、`trashed` 或 `restored`。通过检查此变量的值，你可以确定模型应该针对特定事件广播到哪些频道（如果有）：

```php
/**
 * 获取模型事件应该广播到的频道。
 *
 * @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],
    };
}
```



#### 自定义模型广播事件的创建

有时，你可能希望自定义 Laravel 创建底层模型广播事件的方式。你可以通过在 Eloquent 模型中定义 `newBroadcastableEvent` 方法来实现这一点。此方法应该返回一个 `Illuminate\Database\Eloquent\BroadcastableModelEventOccurred` 实例：

```php
use Illuminate\Database\Eloquent\BroadcastableModelEventOccurred;

/**
 * 为模型创建一个新的可广播模型事件。
 */
protected function newBroadcastableEvent(string $event): BroadcastableModelEventOccurred
{
    return (new BroadcastableModelEventOccurred(
        $this, $event
    ))->dontBroadcastToCurrentUser();
}
```

### 模型广播约定

#### 频道约定

正如你可能已经注意到的，上面的模型示例中的 `broadcastOn` 方法并没有返回 `Channel` 实例，而是直接返回了 Eloquent 模型。如果模型的 `broadcastOn` 方法返回一个 Eloquent 模型实例（或者该实例包含在方法返回的数组中），Laravel 将自动为该模型实例化一个私有频道，并使用模型的类名和主键标识符作为频道名称。

因此，一个 `id` 为 `1` 的 `App\Models\User` 模型将被转换为一个名为 `App.Models.User.1` 的 `Illuminate\Broadcasting\PrivateChannel` 实例。当然，除了从模型的 `broadcastOn` 方法中返回 Eloquent 模型实例外，你还可以返回完整的 `Channel` 实例，以便完全控制模型的频道名称：

```php
use Illuminate\Broadcasting\PrivateChannel;

/**
 * 获取模型事件应该广播到的频道。
 *
 * @return array<int, \Illuminate\Broadcasting\Channel>
 */
public function broadcastOn(string $event): array
{
    return [
        new PrivateChannel('user.'.$this->id)
    ];
}
```

如果你打算从模型的 `broadcastOn` 方法中显式返回一个频道实例，可以将 Eloquent 模型实例传递给频道的构造函数。这样做时，Laravel 将使用上面讨论的模型频道约定，将 Eloquent 模型转换为频道名称字符串：

```php
return [new Channel($this->user)];
```



如果你需要确定模型的频道名称，可以在任何模型实例上调用 `broadcastChannel` 方法。例如，对于一个 `id` 为 `1` 的 `App\Models\User` 模型，此方法将返回字符串 `App.Models.User.1`：

```php
$user->broadcastChannel();
```

#### 事件约定

由于模型广播事件并不与应用程序 `App\Events` 目录中的“实际”事件相关联，因此它们的名称和载荷（payload）是根据约定分配的。Laravel 的约定是使用模型的类名（不包含命名空间）以及触发广播的模型事件名称来广播事件。

因此，例如，对 `App\Models\Post` 模型进行更新时，将向客户端应用程序广播一个名为 `PostUpdated` 的事件，其载荷如下：

```json
{
    "model": {
        "id": 1,
        "title": "My first post"
        ...
    },
    ...
    "socket": "someSocketId"
}
```

删除 `App\Models\User` 模型时，将广播一个名为 `UserDeleted` 的事件。

如果你愿意，可以通过在模型中添加 `broadcastAs` 和 `broadcastWith` 方法来自定义广播名称和载荷。这些方法接收正在发生的模型事件或操作的名称，使你能够针对每个模型操作自定义事件的名称和载荷。如果 `broadcastAs` 方法返回 `null`，Laravel 将使用上面讨论的模型广播事件命名约定来广播事件：

```php
/**
 * 模型事件的广播名称。
 */
public function broadcastAs(string $event): string|null
{
    return match ($event) {
        'created' => 'post.created',
        default => null,
    };
}

/**
 * 获取模型要广播的数据。
 *
 * @return array<string, mixed>
 */
public function broadcastWith(string $event): array
{
    return match ($event) {
        'created' => ['title' => $this->title],
        default => ['model' => $this],
    };
}
```


### 监听模型广播事件

在模型中添加 `BroadcastsEvents` trait 并定义模型的 `broadcastOn` 方法后，就可以在客户端应用程序中监听广播的模型事件了。

在开始之前，你可能需要先查阅完整的[事件监听文档](#listening-for-events)。

首先，使用 `private` 方法获取一个频道实例，然后调用 `listen` 方法监听指定的事件。通常，传递给 `private` 方法的频道名称应遵循 Laravel 的[模型广播约定](#model-broadcasting-conventions)。

获取频道实例后，你可以使用 `listen` 方法监听特定事件。由于模型广播事件并不关联应用程序 `App\Events\` 目录中的某个实际事件，因此，[事件名称](#model-broadcasting-event-conventions)必须以 `.` 作为前缀，以表明该事件不属于任何特定的命名空间。

每个模型广播事件都包含一个 `model` 属性，其中包含模型所有可广播的属性：

```js
Echo.private(`App.Models.User.${this.user.id}`)
    .listen('.UserUpdated', (e) => {
        console.log(e.model);
    });
```

#### 使用 React、Vue 或 Svelte

如果你使用的是 React、Vue 或 Svelte，可以使用 Laravel Echo 内置的 `useEchoModel` Hook，轻松监听模型广播事件：

```js tab=React
import { useEchoModel } from "@laravel/echo-react";

useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => {
    console.log(e.model);
});
```

```vue tab=Vue
<script setup lang="ts">
import { useEchoModel } from "@laravel/echo-vue";

useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => {
    console.log(e.model);
});
</script>
```

```svelte tab=Svelte
<script>
import { useEchoModel } from "@laravel/echo-svelte";

useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => {
    console.log(e.model);
});
</script>
```



你还可以指定模型事件负载数据（Payload）的结构，从而获得更完善的类型安全支持和更便捷的代码编辑体验：

```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](https://pusher.com/channels?utm_source=chatgpt.com) 时，你必须在 [应用程序控制面板](https://dashboard.pusher.com/?utm_source=chatgpt.com) 的「App Settings」（应用设置）部分启用「Client Events」（客户端事件）选项，才能发送客户端事件。

有时，你可能希望在完全不经过 Laravel 应用程序的情况下，直接向其他已连接的客户端广播事件。

这对于诸如「正在输入」通知之类的功能特别有用。例如，当某个用户正在特定页面输入消息时，你可以通知应用程序中的其他用户。

要广播客户端事件，可以使用 Echo 的 `whisper` 方法：

```js tab=JavaScript
Echo.private(`chat.${roomId}`)
    .whisper('typing', {
        name: this.user.name
    });
```

```js tab=React
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 tab=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 tab=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` 方法：

```js tab=JavaScript
Echo.private(`chat.${roomId}`)
    .listenForWhisper('typing', (e) => {
        console.log(e.name);
    });
```

```js tab=React
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 tab=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 tab=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>
```

## 通知

通过将事件广播与[通知功能](/docs/laravel/13.x/notifications)结合使用，你的 JavaScript 应用程序可以在新通知产生时立即接收通知，而无需刷新页面。

在开始之前，请务必阅读有关使用[广播通知频道](/docs/laravel/13.x/notifications#broadcast-notifications)的文档。

配置通知使用广播频道后，你就可以使用 Echo 的 `notification` 方法监听广播事件。

请注意，频道名称应与接收通知的实体类名称保持一致：

```js tab=JavaScript
Echo.private(`App.Models.User.${userId}`)
    .notification((notification) => {
        console.log(notification.type);
    });
```

```js tab=React
import { useEchoModel } from "@laravel/echo-react";

const { channel } = useEchoModel('App.Models.User', userId);

channel().notification((notification) => {
    console.log(notification.type);
});
```

```vue tab=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 tab=Svelte
<script>
import { useEchoModel } from "@laravel/echo-svelte";

const { channel } = useEchoModel('App.Models.User', userId);

channel().notification((notification) => {
    console.log(notification.type);
});
</script>
```



在此示例中，所有通过 `broadcast` 频道发送给 `App\Models\User` 实例的通知，都会被该回调函数接收。应用程序的 `routes/channels.php` 文件中包含了针对 `App.Models.User.{id}` 频道的频道授权回调函数。

#### 停止监听通知

如果你希望停止监听通知，但又不想[离开频道](#leaving-a-channel)，可以使用 `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);
```

