1. Profundizando
  2. Difusión

Introducción

En muchas aplicaciones web modernas, se utilizan WebSockets para implementar interfaces de usuario en tiempo real y actualizadas al instante. Cuando algunos datos se actualizan en el servidor, típicamente se envía un mensaje a través de una conexión WebSocket para ser manejado por el cliente. Los WebSockets proporcionan una alternativa más eficiente que estar consultando continuamente el servidor de tu aplicación en busca de cambios de datos que deben reflejarse en tu interfaz de usuario.

Por ejemplo, imagina que tu aplicación puede exportar los datos de un usuario a un archivo CSV y enviarlo por correo electrónico. Sin embargo, la creación de este archivo CSV lleva varios minutos, así que eliges crear y enviar el CSV dentro de un trabajo en cola. Cuando el CSV se haya creado y enviado por correo al usuario, podemos usar la difusión de eventos para despachar un evento App\Events\UserDataExported que sea recibido por JavaScript de nuestra aplicación. Una vez que se recibe el evento, podemos mostrar un mensaje al usuario de que su CSV se le ha enviado por correo electrónico sin que nunca tenga que actualizar la página.

Para ayudarte a construir este tipo de características, Laravel facilita la "difusión" de tus eventos Laravel del lado del servidor sobre una conexión WebSocket. La difusión de tus eventos Laravel te permite compartir los mismos nombres y datos de eventos entre tu aplicación Laravel del lado del servidor y tu aplicación JavaScript del lado del cliente.

Los conceptos fundamentales detrás de la difusión son simples: los clientes se conectan a canales nombrados en el frontend, mientras que tu aplicación Laravel difunde eventos a estos canales en el backend. Estos eventos pueden contener cualquier dato adicional que desees poner a disposición del frontend.

Controladores Compatibles

De forma predeterminada, Laravel incluye dos controladores de difusión del lado del servidor entre los que puedes elegir: Pusher Channels y Ably. Sin embargo, los paquetes impulsados por la comunidad, como laravel-websockets y soketi, proporcionan controladores de difusión adicionales que no requieren proveedores comerciales de difusión.

Nota Antes de sumergirte en la difusión de eventos, asegúrate de haber leído la documentación de Laravel sobre eventos y listeners.

Instalación del Lado del Servidor

Para comenzar a usar la difusión de eventos de Laravel, necesitamos hacer algunas configuraciones dentro de la aplicación Laravel, así como instalar algunos paquetes.

La difusión de eventos se realiza mediante un controlador de difusión del lado del servidor que difunde tus eventos de Laravel para que Laravel Echo (una biblioteca de JavaScript) pueda recibirlos dentro del cliente del navegador. No te preocupes: repasaremos cada parte del proceso de instalación paso a paso.

Configuración

Toda la configuración de difusión de eventos de tu aplicación está almacenada en el archivo de configuración config/broadcasting.php. Laravel admite varios controladores de difusión de forma predeterminada: Pusher Channels, Redis y un controlador log para desarrollo y depuración local. Además, se incluye un controlador null que te permite desactivar por completo la difusión durante las pruebas. En el archivo de configuración config/broadcasting.php se incluye un ejemplo de configuración para cada uno de estos controladores.

Proveedor de Servicios de Difusión

Antes de difundir eventos, primero deberás registrar el App\Providers\BroadcastServiceProvider. En las nuevas aplicaciones de Laravel, solo necesitas descomentar este proveedor en el array providers de tu archivo de configuración config/app.php. Este BroadcastServiceProvider contiene el código necesario para registrar las rutas y devoluciones de llamada de autorización de difusión.

Configuración de la Cola

También deberás configurar y ejecutar un trabajador de cola. Todas las difusiones de eventos se realizan mediante trabajos en cola para que el tiempo de respuesta de tu aplicación no se vea afectado seriamente por los eventos que se difunden.

Canales de Pusher

Si planeas difundir tus eventos usando Pusher Channels, deberías instalar el SDK de PHP de Pusher Channels usando el administrador de paquetes Composer:

composer require pusher/pusher-php-server

A continuación, debes configurar tus credenciales de Pusher Channels en el archivo de configuración config/broadcasting.php. Ya se incluye un ejemplo de configuración de Pusher Channels en este archivo, lo que te permite especificar rápidamente tu clave, secreto e ID de aplicación. Normalmente, estos valores deben establecerse a través de las variables de entorno PUSHER_APP_KEY, PUSHER_APP_SECRET y PUSHER_APP_ID:

PUSHER_APP_ID=your-pusher-app-id
PUSHER_APP_KEY=your-pusher-key
PUSHER_APP_SECRET=your-pusher-secret
PUSHER_APP_CLUSTER=mt1

La configuración pusher del archivo config/broadcasting.php también te permite especificar options adicionales que son compatibles con Channels, como el clúster.

A continuación, deberás cambiar el controlador de difusión a pusher en tu archivo .env:

BROADCAST_DRIVER=pusher

Finalmente, estás listo para instalar y configurar Laravel Echo, que recibirá los eventos de difusión en el lado del cliente.

Alternativas Open Source a Pusher

Los paquetes laravel-websockets y soketi proporcionan servidores WebSocket compatibles con Pusher para Laravel. Estos paquetes te permiten aprovechar toda la potencia de la difusión de Laravel sin necesidad de un proveedor comercial de WebSocket. Para obtener más información sobre la instalación y el uso de estos paquetes, consulta nuestra documentación sobre alternativas de código abierto.

Ably

Nota La documentación a continuación discute cómo usar Ably en modo de "compatibilidad con Pusher". Sin embargo, el equipo de Ably recomienda y mantiene un emisor y cliente de Echo que puede aprovechar las capacidades únicas ofrecidas por Ably. Para obtener más información sobre el uso de los controladores mantenidos por Ably, consulta la documentación de Laravel broadcaster de Ably.

Si planeas difundir tus eventos usando Ably, debes instalar el SDK de PHP de Ably mediante el administrador de paquetes Composer:

composer require ably/ably-php

A continuación, debes configurar tus credenciales de Ably en el archivo de configuración config/broadcasting.php. Un ejemplo de configuración de Ably ya está incluido en este archivo, lo que te permite especificar rápidamente tu clave. Típicamente, este valor debería establecerse a través de la variable de entorno ABLY_KEY:

ABLY_KEY=your-ably-key

A continuación, deberás cambiar tu controlador de difusión a ably en tu archivo .env:

BROADCAST_DRIVER=ably

Finalmente, estás listo para instalar y configurar Laravel Echo, que recibirá los eventos de difusión en el lado del cliente.

Alternativas de Código Abierto

PHP

El paquete laravel-websockets es un paquete puro de PHP compatible con Pusher para WebSocket para Laravel. Este paquete te permite aprovechar toda la potencia de la difusión de Laravel sin necesidad de un proveedor comercial de WebSocket. Para obtener más información sobre la instalación y el uso de este paquete, consulta su documentación oficial.

Node

Soketi es un servidor WebSocket compatible con Pusher basado en Node para Laravel. En el fondo, Soketi utiliza µWebSockets.js para una escalabilidad y velocidad extremas. Este paquete te permite aprovechar toda la potencia de la difusión de Laravel sin necesidad de un proveedor comercial de WebSocket. Para obtener más información sobre la instalación y el uso de este paquete, consulta su documentación oficial.

Instalación del Lado del Cliente

Canales de Pusher

Laravel Echo es una biblioteca de JavaScript que facilita la suscripción a canales y la escucha de eventos emitidos por tu controlador de difusión del lado del servidor. Puedes instalar Echo a través del administrador de paquetes NPM. En este ejemplo, también instalaremos el paquete pusher-js ya que usaremos el emisor Pusher Channels:

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

Una vez que Echo esté instalado, estás listo para crear una nueva instancia de Echo en el JavaScript de tu aplicación. Un buen lugar para hacer esto es al final del archivo resources/js/bootstrap.js que se incluye con el framework Laravel. Por defecto, este archivo ya incluye una configuración de ejemplo de Echo; simplemente necesitas descomentarla:

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
});

Una vez que hayas descomentado y ajustado la configuración de Echo según tus necesidades, puedes compilar los activos de tu aplicación:

npm run dev

Nota Para obtener más información sobre cómo compilar los activos JavaScript de tu aplicación, consulta la documentación sobre Vite.

Usando una Instancia de Cliente Existente

Si ya tienes una instancia de cliente Pusher Channels preconfigurada que te gustaría que Echo utilice, puedes pasarla a Echo a través de la opción de configuración client:

import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
 
const options = {
broadcaster: 'pusher',
key: 'your-pusher-channels-key'
}
 
window.Echo = new Echo({
...options,
client: new Pusher(options.key, options)
});

Ably

Nota La documentación a continuación discute cómo usar Ably en modo de "compatibilidad con Pusher". Sin embargo, el equipo de Ably recomienda y mantiene un emisor y cliente de Echo que puede aprovechar las capacidades únicas ofrecidas por Ably. Para obtener más información sobre el uso de los controladores mantenidos por Ably, consulta la documentación de Laravel broadcaster de Ably.

Laravel Echo es una biblioteca de JavaScript que facilita la suscripción a canales y la escucha de eventos emitidos por tu controlador de difusión del lado del servidor. Puedes instalar Echo a través del administrador de paquetes NPM. En este ejemplo, también instalaremos el paquete pusher-js.

Puede que te preguntes por qué instalaríamos la biblioteca JavaScript pusher-js aunque estamos utilizando Ably para difundir nuestros eventos. Afortunadamente, Ably incluye un modo de compatibilidad con Pusher que nos permite usar el protocolo Pusher al escuchar eventos en nuestra aplicación del lado del cliente:

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

Antes de continuar, debes habilitar el soporte del protocolo Pusher en la configuración de tu aplicación Ably. Puedes habilitar esta función dentro de la sección "Protocol Adapter Settings" de la pantalla de configuración de tu aplicación Ably.

Una vez que Echo esté instalado, estás listo para crear una nueva instancia de Echo en el JavaScript de tu aplicación. Un buen lugar para hacer esto es al final del archivo resources/js/bootstrap.js que se incluye con el framework Laravel. Por defecto, este archivo ya incluye una configuración de ejemplo de Echo; sin embargo, la configuración predeterminada en el archivo bootstrap.js está destinada a Pusher. Puedes copiar la configuración a continuación para transicionar tu configuración a Ably:

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

Ten en cuenta que nuestra configuración de Ably Echo hace referencia a una variable de entorno VITE_ABLY_PUBLIC_KEY. El valor de esta variable debería ser tu clave pública de Ably. Tu clave pública es la parte de tu clave de Ably que ocurre antes del carácter :.

Una vez que hayas descomentado y ajustado la configuración de Echo según tus necesidades, puedes compilar los activos de tu aplicación:

npm run dev

Nota Para obtener más información sobre cómo compilar los activos JavaScript de tu aplicación, consulta la documentación sobre Vite.

Resumen del Concepto

La difusión de eventos de Laravel te permite transmitir tus eventos Laravel del lado del servidor a tu aplicación JavaScript del lado del cliente utilizando un enfoque basado en controladores para WebSockets. Actualmente, Laravel incluye controladores para Pusher Channels y Ably. Los eventos se pueden consumir fácilmente en el lado del cliente utilizando el paquete JavaScript Laravel Echo.

Los eventos se transmiten a través de "canales", que pueden ser públicos o privados. Cualquier visitante de tu aplicación puede suscribirse a un canal público sin necesidad de autenticación o autorización; sin embargo, para suscribirse a un canal privado, un usuario debe estar autenticado y autorizado para escuchar en ese canal.

Nota Si deseas explorar alternativas de código abierto a Pusher, consulta las alternativas de código abierto.

Usando una Aplicación de Ejemplo

Antes de sumergirnos en cada componente de la difusión de eventos, echemos un vistazo general utilizando como ejemplo una tienda de comercio electrónico.

En nuestra aplicación, supongamos que tenemos una página que permite a los usuarios ver el estado de envío de sus pedidos. También supongamos que se dispara un evento OrderShipmentStatusUpdated cuando se procesa una actualización del estado de envío en la aplicación:

use App\Events\OrderShipmentStatusUpdated;
 
OrderShipmentStatusUpdated::dispatch($order);

La Interfaz ShouldBroadcast

Cuando un usuario está viendo uno de sus pedidos, no queremos que tenga que actualizar la página para ver las actualizaciones de estado. En su lugar, queremos transmitir las actualizaciones a la aplicación a medida que se crean. Entonces, necesitamos marcar el evento OrderShipmentStatusUpdated con la interfaz ShouldBroadcast. Esto instruirá a Laravel a transmitir el evento cuando se dispare:

<?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
{
/**
* La instancia del pedido.
*
* @var \App\Order
*/
public $order;
}

La interfaz ShouldBroadcast requiere que nuestro evento defina un método broadcastOn. Este método es responsable de devolver los canales en los que el evento debe transmitirse. Un esbozo vacío de este método ya está definido en las clases de eventos generadas, por lo que solo necesitamos completar sus detalles. Solo queremos que el creador del pedido pueda ver las actualizaciones de estado, así que transmitiremos el evento en un canal privado vinculado al pedido:

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PrivateChannel;
 
/**
* Obtener el canal en el que se debe emitir el evento.
*/
public function broadcastOn(): Channel
{
return new PrivateChannel('orders.'.$this->order->id);
}

Si deseas que el evento se transmita en varios canales, puedes devolver un array en su lugar:

use Illuminate\Broadcasting\PrivateChannel;
 
/**
* Obtener los canales en los que se debe emitir el evento.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('orders.'.$this->order->id),
// ...
];
}

Autorización de Canales

Recuerda, los usuarios deben estar autorizados para escuchar en canales privados. Podemos definir nuestras reglas de autorización de canal en el archivo routes/channels.php de nuestra aplicación. En este ejemplo, necesitamos verificar que cualquier usuario que intente escuchar en el canal privado orders.1 sea realmente el creador del pedido:

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;
});

El método channel acepta dos argumentos: el nombre del canal y un callback que devuelve true o false indicando si el usuario está autorizado para escuchar en el canal.

Todos los callbacks de autorización reciben al usuario autenticado actualmente como su primer argumento y cualquier parámetro de comodín adicional como sus argumentos subsiguientes. En este ejemplo, estamos usando el marcador de posición {orderId} para indicar que la parte "ID" del nombre del canal es un comodín.

Escuchar Difusiones de Eventos

A continuación, lo único que queda es escuchar el evento en nuestra aplicación JavaScript. Podemos hacer esto usando Laravel Echo. Primero, usaremos el método private para suscribirnos al canal privado. Luego, podemos usar el método listen para escuchar el evento OrderShipmentStatusUpdated. Por defecto, todas las propiedades públicas del evento se incluirán en el evento transmitido:

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

Definición de Eventos de Difusión

Para informar a Laravel que un evento dado debe transmitirse, debes implementar la interfaz Illuminate\Contracts\Broadcasting\ShouldBroadcast en la clase del evento. Esta interfaz ya está importada en todas las clases de eventos generadas por el framework, por lo que puedes agregarla fácilmente a cualquiera de tus eventos.

La interfaz ShouldBroadcast requiere que implementes un solo método: broadcastOn. El método broadcastOn debe devolver un canal o un array de canales en los que el evento debe transmitirse. Los canales deben ser instancias de Channel, PrivateChannel o PresenceChannel. Las instancias de Channel representan canales públicos a los que cualquier usuario puede suscribirse, mientras que PrivateChannels y PresenceChannels representan canales privados que requieren autorización de canal:

<?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;
 
/**
* Crear una nueva instancia de evento.
*/
public function __construct(
public User $user,
) {}
 
/**
* Obtener los canales en los que se debe emitir el evento.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('user.'.$this->user->id),
];
}
}

Después de implementar la interfaz ShouldBroadcast, solo necesitas disparar el evento como lo harías normalmente. Una vez que se haya disparado el evento, un trabajo en cola transmitirá automáticamente el evento usando tu controlador de difusión especificado.

Nombre de Difusión

De forma predeterminada, Laravel transmitirá el evento usando el nombre de clase del evento. Sin embargo, puedes personalizar el nombre de la transmisión definiendo un método broadcastAs en el evento:

/**
* El nombre de la emisión del evento.
*/
public function broadcastAs(): string
{
return 'server.created';
}

Si personalizas el nombre de la transmisión usando el método broadcastAs, asegúrate de registrar tu escucha con un carácter . líder. Esto indicará a Echo que no debe agregar el espacio de nombres de la aplicación al evento:

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

Datos de Difusión

Cuando se transmite un evento, todas sus propiedades public se serializan automáticamente y se transmiten como la carga útil del evento, lo que te permite acceder a cualquiera de sus datos públicos desde tu aplicación JavaScript. Entonces, por ejemplo, si tu evento tiene una única propiedad pública $user que contiene un modelo Eloquent, la carga útil del evento transmitido sería:

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

Sin embargo, si deseas tener un control más detallado sobre la carga útil de difusión, puedes agregar un método broadcastWith a tu evento. Este método debe devolver el array de datos que deseas transmitir como la carga útil del evento:

/**
* Obtener los datos para la emisión.
*
* @return array<string, mixed>
*/
public function broadcastWith(): array
{
return ['id' => $this->user->id];
}

Cola de Difusión

De forma predeterminada, cada evento de difusión se coloca en la cola predeterminada para la conexión de cola predeterminada especificada en tu archivo de configuración queue.php. Puedes personalizar la conexión y el nombre de la cola que utiliza el controlador definiendo las propiedades connection y queue en la clase de tu evento:

/**
* El nombre de la conexión de la cola que se utilizará al emitir el evento.
*
* @var string
*/
public $connection = 'redis';
 
/**
* El nombre de la cola en la que se colocará el trabajo de emisión.
*
* @var string
*/
public $queue = 'default';

Alternativamente, puedes personalizar el nombre de la cola definiendo un método broadcastQueue en tu evento:

/**
* El nombre de la cola en la que se colocará el trabajo de emisión.
*/
public function broadcastQueue(): string
{
return 'default';
}

Si deseas difundir tu evento utilizando la cola sync en lugar del controlador de cola predeterminado, puedes implementar la interfaz ShouldBroadcastNow en lugar de ShouldBroadcast:

<?php
 
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
 
class OrderShipmentStatusUpdated implements ShouldBroadcastNow
{
// ...
}

Condiciones de Difusión

A veces, deseas difundir tu evento solo si se cumple una condición determinada. Puedes definir estas condiciones agregando un método broadcastWhen a la clase de tu evento:

/**
* Determinar si este evento debe emitirse.
*/
public function broadcastWhen(): bool
{
return $this->order->value > 100;
}

Difusión y Transacciones de Base de Datos

Cuando los eventos de difusión se despachan dentro de transacciones de base de datos, pueden procesarse por la cola antes de que la transacción de la base de datos se haya confirmado. Cuando esto sucede, las actualizaciones que hayas realizado en los modelos o registros de la base de datos durante la transacción de la base de datos pueden no reflejarse aún en la base de datos. Además, los modelos o registros de la base de datos creados dentro de la transacción pueden no existir en la base de datos. Si tu evento depende de estos modelos, pueden ocurrir errores inesperados cuando se procesa el trabajo que difunde el evento.

Si la opción de configuración after_commit de tu conexión de cola está establecida en false, aún puedes indicar que un evento de difusión en particular debe despacharse después de que se hayan confirmado todas las transacciones de la base de datos abiertas mediante la implementación de la interfaz ShouldDispatchAfterCommit en la clase del evento:

<?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;
}

Nota Para obtener más información sobre cómo solucionar estos problemas, revisa la documentación sobre trabajos en cola y transacciones de base de datos.

Autorización de Canales

Los canales privados requieren que autorices que el usuario actualmente autenticado realmente pueda escuchar en el canal. Esto se logra haciendo una solicitud HTTP a tu aplicación Laravel con el nombre del canal y permitiendo que tu aplicación determine si el usuario puede escuchar en ese canal. Al usar Laravel Echo, la solicitud HTTP para autorizar suscripciones a canales privados se realizará automáticamente; sin embargo, necesitas definir las rutas adecuadas para responder a estas solicitudes.

Definir Rutas de Autorización

Afortunadamente, Laravel facilita la definición de las rutas para responder a las solicitudes de autorización de canal. En el App\Providers\BroadcastServiceProvider incluido en tu aplicación Laravel, verás una llamada al método Broadcast::routes. Este método registrará la ruta /broadcasting/auth para manejar las solicitudes de autorización:

Broadcast::routes();

El método Broadcast::routes colocará automáticamente sus rutas dentro del grupo de middleware web; sin embargo, puedes pasar un array de atributos de ruta al método si deseas personalizar los atributos asignados:

Broadcast::routes($attributes);

Personalización del Punto Final de Autorización

De forma predeterminada, Echo usará el punto final /broadcasting/auth para autorizar el acceso a los canales. Sin embargo, puedes especificar tu propio punto final de autorización pasando la opción de configuración authEndpoint a tu instancia de Echo:

window.Echo = new Echo({
broadcaster: 'pusher',
// ...
authEndpoint: '/custom/endpoint/auth'
});

Personalización de la Solicitud de Autorización

Puedes personalizar la forma en que Laravel Echo realiza solicitudes de autorización proporcionando un autorizador personalizado al inicializar Echo:

window.Echo = new Echo({
// ...
authorizer: (channel, options) => {
return {
authorize: (socketId, callback) => {
axios.post('/api/broadcasting/auth', {
socket_id: socketId,
channel_name: channel.name
})
.then(response => {
callback(null, response.data);
})
.catch(error => {
callback(error);
});
}
};
},
})

Definir Devoluciones de Llamada de Autorización

A continuación, necesitamos definir la lógica que determinará si el usuario actualmente autenticado puede escuchar en un canal dado. Esto se hace en el archivo routes/channels.php que se incluye con tu aplicación. En este archivo, puedes usar el método Broadcast::channel para registrar devoluciones de llamada de autorización de canal:

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

El método channel acepta dos argumentos: el nombre del canal y un callback que devuelve true o false indicando si el usuario está autorizado para escuchar en el canal.

Todos los callbacks de autorización reciben al usuario autenticado actualmente como su primer argumento y cualquier parámetro de comodín adicional como sus argumentos subsiguientes. En este ejemplo, estamos usando el marcador de posición {orderId} para indicar que la parte "ID" del nombre del canal es un comodín.

Puedes ver una lista de las devoluciones de llamada de autorización de difusión de tu aplicación utilizando el comando Artisan channel:list:

php artisan channel:list

Enlace de Modelos de Devolución de Llamada de Autorización

Al igual que las rutas HTTP, las rutas de canal también pueden aprovechar el enlace de modelos implícito y explícito. Por ejemplo, en lugar de recibir un ID de pedido en forma de cadena o número, puedes solicitar una instancia real del modelo Order:

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

Advertencia A diferencia del enlace de modelos de ruta HTTP, el enlace de modelos de canal no admite restricción automática de enlace de modelo implícita. Sin embargo, esto rara vez es un problema porque la mayoría de los canales se pueden limitar en función de la clave principal única de un solo modelo.

Autenticación de Devolución de Llamada de Autorización

Los canales de difusión privados y de presencia autentican al usuario actual a través del guardia de autenticación predeterminado de tu aplicación. Si el usuario no está autenticado, la autorización del canal se deniega automáticamente y la devolución de llamada de autorización nunca se ejecuta. Sin embargo, puedes asignar varios guardias personalizados que deben autenticar la solicitud entrante si es necesario:

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

Definir Clases de Canal

Si tu aplicación consume muchos canales diferentes, tu archivo routes/channels.php podría volverse voluminoso. Entonces, en lugar de usar cierres para autorizar canales, puedes usar clases de canal. Para generar una clase de canal, utiliza el comando Artisan make:channel. Este comando colocará una nueva clase de canal en el directorio App/Broadcasting.

php artisan make:channel OrderChannel

A continuación, registra tu canal en tu archivo routes/channels.php:

use App\Broadcasting\OrderChannel;
 
Broadcast::channel('orders.{order}', OrderChannel::class);

Finalmente, puedes colocar la lógica de autorización para tu canal en el método join de la clase del canal. Este método join contendrá la misma lógica que normalmente habrías colocado en tu cierre de autorización de canal. También puedes aprovechar la unión de modelos de canal:

<?php
 
namespace App\Broadcasting;
 
use App\Models\Order;
use App\Models\User;
 
class OrderChannel
{
/**
* Crear una nueva instancia de canal.
*/
public function __construct()
{
// ...
}
 
/**
* Autenticar el acceso del usuario al canal.
*/
public function join(User $user, Order $order): array|bool
{
return $user->id === $order->user_id;
}
}

Nota Al igual que muchas otras clases en Laravel, las clases de canal se resolverán automáticamente mediante el contenedor de servicios. Por lo tanto, puedes hacer type-hint de cualquier dependencia requerida por tu canal en su constructor.

Eventos de Difusión

Una vez que hayas definido un evento y lo hayas marcado con la interfaz ShouldBroadcast, solo necesitas despachar el evento utilizando el método de despacho del evento. El despachador de eventos notará que el evento está marcado con la interfaz ShouldBroadcast y encolará el evento para su difusión:

use App\Events\OrderShipmentStatusUpdated;
 
OrderShipmentStatusUpdated::dispatch($order);

Solo a Otros

Al construir una aplicación que utiliza la difusión de eventos, ocasionalmente es posible que necesites difundir un evento a todos los suscriptores de un canal determinado excepto el usuario actual. Puedes lograr esto usando el ayudante broadcast y el método toOthers:

use App\Events\OrderShipmentStatusUpdated;
 
broadcast(new OrderShipmentStatusUpdated($update))->toOthers();

Para entender mejor cuándo puedes querer usar el método toOthers, imaginemos una aplicación de lista de tareas donde un usuario puede crear una nueva tarea ingresando un nombre de tarea. Para crear una tarea, tu aplicación podría realizar una solicitud a una URL /task que difunde la creación de la tarea y devuelve una representación JSON de la nueva tarea. Cuando tu aplicación JavaScript recibe la respuesta del punto final, podría insertar directamente la nueva tarea en su lista de tareas de la siguiente manera:

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

Sin embargo, recuerda que también difundimos la creación de la tarea. Si tu aplicación JavaScript también está escuchando este evento para agregar tareas a la lista de tareas, tendrás tareas duplicadas en tu lista: una del punto final y otra de la difusión. Puedes resolver esto usando el método toOthers para instruir al difusor que no difunda el evento al usuario actual.

Advertencia Tu evento debe usar el rasgo Illuminate\Broadcasting\InteractsWithSockets para poder llamar al método toOthers.

Configuración

Cuando inicializas una instancia de Laravel Echo, se asigna un ID de socket a la conexión. Si estás utilizando una instancia global de Axios para realizar solicitudes HTTP desde tu aplicación JavaScript, el ID de socket se adjuntará automáticamente a cada solicitud saliente como encabezado X-Socket-ID. Luego, cuando llamas al método toOthers, Laravel extraerá el ID de socket del encabezado e instruirá al difusor que no difunda a ninguna conexión con ese ID de socket.

Si no estás utilizando una instancia global de Axios, deberás configurar manualmente tu aplicación JavaScript para enviar el encabezado X-Socket-ID con todas las solicitudes salientes. Puedes recuperar el ID de socket utilizando el método Echo.socketId:

var socketId = Echo.socketId();

Personalización de la Conexión

Si tu aplicación interactúa con múltiples conexiones de difusión y deseas difundir un evento usando un difusor que no sea el predeterminado, puedes especificar a qué conexión enviar un evento mediante el método via:

use App\Events\OrderShipmentStatusUpdated;
 
broadcast(new OrderShipmentStatusUpdated($update))->via('pusher');

Alternativamente, puedes especificar la conexión de difusión del evento llamando al método broadcastVia dentro del constructor del evento. Sin embargo, antes de hacerlo, debes asegurarte de que la clase del evento use el rasgo InteractsWithBroadcasting:

<?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;
 
/**
* Crear una nueva instancia de evento.
*/
public function __construct()
{
$this->broadcastVia('pusher');
}
}

Recibir Difusiones

Escuchar Eventos

Una vez que hayas instalado e inicializado Laravel Echo, estarás listo para comenzar a escuchar eventos que se difunden desde tu aplicación Laravel. Primero, utiliza el método channel para obtener una instancia de un canal, luego llama al método listen para escuchar un evento específico:

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

Si deseas escuchar eventos en un canal privado, utiliza el método private en su lugar. Puedes seguir encadenando llamadas al método listen para escuchar varios eventos en un solo canal:

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

Dejar de Escuchar Eventos

Si deseas dejar de escuchar un evento específico sin abandonar el canal, puedes usar el método stopListening:

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

Dejar un Canal

Para abandonar un canal, puedes llamar al método leaveChannel en tu instancia de Echo:

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

Si deseas abandonar un canal y también sus canales privados y de presencia asociados, puedes llamar al método leave:

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

Espacios de Nombres

Puede que hayas notado en los ejemplos anteriores que no especificamos el espacio de nombres completo App\Events para las clases de eventos. Esto se debe a que Echo asumirá automáticamente que los eventos están ubicados en el espacio de nombres App\Events. Sin embargo, puedes configurar el espacio de nombres raíz al instanciar Echo pasando una opción de configuración namespace:

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

Alternativamente, puedes agregar un prefijo a las clases de eventos con un . al suscribirte a ellas usando Echo. Esto te permitirá especificar siempre el nombre de clase completamente cualificado:

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

Canales de Presencia

Los canales de presencia se construyen sobre la seguridad de los canales privados y, al mismo tiempo, ofrecen la característica adicional de tener conocimiento de quién está suscrito al canal. Esto facilita la creación de funciones potentes y colaborativas, como notificar a los usuarios cuando otro usuario está viendo la misma página o listar los habitantes de una sala de chat.

Autorización de Canales de Presencia

Todos los canales de presencia también son canales privados; por lo tanto, los usuarios deben estar autorizados para acceder a ellos. Sin embargo, al definir devoluciones de llamada de autorización para canales de presencia, no devolverás true si el usuario está autorizado para unirse al canal. En su lugar, debes devolver un array de datos sobre el usuario.

Los datos devueltos por la devolución de llamada de autorización estarán disponibles para los escuchadores de eventos del canal de presencia en tu aplicación JavaScript. Si el usuario no está autorizado para unirse al canal de presencia, debes devolver false o null:

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

Unirse a Canales de Presencia

Para unirte a un canal de presencia, puedes usar el método join de Echo. El método join devolverá una implementación de PresenceChannel que, además de exponer el método listen, te permite suscribirte a los eventos here, joining y leaving.

Echo.join(`chat.${roomId}`)
.here((users) => {
// ...
})
.joining((user) => {
console.log(user.name);
})
.leaving((user) => {
console.log(user.name);
})
.error((error) => {
console.error(error);
});

La devolución de llamada here se ejecutará inmediatamente una vez que el canal se haya unido correctamente y recibirá un array que contiene la información del usuario para todos los demás usuarios actualmente suscritos al canal. El método joining se ejecutará cuando un nuevo usuario se una a un canal, mientras que el método leaving se ejecutará cuando un usuario abandone el canal. El método error se ejecutará cuando el punto final de autenticación devuelva un código de estado HTTP que no sea 200 o si hay un problema al analizar el JSON devuelto.

Difundir a Canales de Presencia

Los canales de presencia pueden recibir eventos al igual que los canales públicos o privados. Usando el ejemplo de una sala de chat, es posible que deseemos difundir eventos NewMessage al canal de presencia de la sala. Para hacerlo, devolveremos una instancia de PresenceChannel desde el método broadcastOn del evento:

/**
* Obtener los canales en los que se debe emitir el evento.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PresenceChannel('chat.'.$this->message->room_id),
];
}

Al igual que con otros eventos, puedes usar el ayudante broadcast y el método toOthers para excluir al usuario actual de recibir la difusión:

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

Como es típico en otros tipos de eventos, puedes escuchar eventos enviados a canales de presencia usando el método listen de Echo:

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

Difusión de Modelos

Advertencia Antes de leer la siguiente documentación sobre la difusión de modelos, te recomendamos que te familiarices con los conceptos generales de los servicios de difusión de modelos de Laravel, así como con la forma de crear y escuchar manualmente eventos de difusión.

Es común difundir eventos cuando los modelos Eloquent de tu aplicación se crean, actualizan o eliminan. Por supuesto, esto se puede lograr fácilmente mediante la definición manual de eventos personalizados para cambios de estado en modelos Eloquent y marcándolos con la interfaz ShouldBroadcast.

Sin embargo, si no estás utilizando estos eventos para ningún otro propósito en tu aplicación, puede ser engorroso crear clases de eventos con el único propósito de difundirlos. Para solucionar esto, Laravel te permite indicar que un modelo Eloquent debería difundir automáticamente sus cambios de estado.

Para empezar, tu modelo Eloquent debe utilizar el rasgo Illuminate\Database\Eloquent\BroadcastsEvents. Además, el modelo debe definir un método broadcastOn, que devolverá un array de canales en los que los eventos del modelo deben difundirse:

<?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;
 
/**
* Obtener el usuario al que pertenece la publicación.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
 
/**
* Obtener los canales en los que se deben emitir eventos del modelo.
*
* @return array<int, \Illuminate\Broadcasting\Channel|\Illuminate\Database\Eloquent\Model>
*/
public function broadcastOn(string $event): array
{
return [$this, $this->user];
}
}

Una vez que tu modelo incluye este rasgo y define sus canales de difusión, comenzará automáticamente a difundir eventos cuando se cree, actualice, elimine, archive o restaure una instancia del modelo.

Además, es posible que hayas notado que el método broadcastOn del modelo recibe un argumento de cadena $event. Este argumento contiene el tipo de evento que ha ocurrido en el modelo y tendrá un valor de created, updated, deleted, trashed o restored. Al inspeccionar el valor de esta variable, puedes determinar a qué canales (si los hay) el modelo debe difundir para un evento en particular:

/**
* Obtener los canales en los que se deben emitir eventos del modelo.
*
* @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],
};
}

Personalización de la Creación de Eventos de Difusión de Modelos

Ocasionalmente, es posible que desees personalizar cómo Laravel crea el evento de difusión de modelo subyacente. Puedes lograr esto definiendo un método newBroadcastableEvent en tu modelo Eloquent. Este método debe devolver una instancia de Illuminate\Database\Eloquent\BroadcastableModelEventOccurred:

use Illuminate\Database\Eloquent\BroadcastableModelEventOccurred;
 
/**
* Crear una nueva instancia de evento de modelo emitible para el modelo.
*/
protected function newBroadcastableEvent(string $event): BroadcastableModelEventOccurred
{
return (new BroadcastableModelEventOccurred(
$this, $event
))->dontBroadcastToCurrentUser();
}

Convenciones de Difusión de Modelos

Convenciones de Canales

Como habrás notado, el método broadcastOn en el ejemplo del modelo no devolvió instancias de Channel. En su lugar, se devolvieron directamente modelos Eloquent. Si el método broadcastOn de tu modelo devuelve una instancia de modelo Eloquent (o está contenida en un array devuelto por el método), Laravel convertirá automáticamente la instancia en una instancia de canal privado para el modelo, utilizando el nombre de clase del modelo y el identificador de clave primaria del modelo como nombre del canal.

Así, una instancia de modelo App\Models\User con un id de 1 se convertiría en una instancia de Illuminate\Broadcasting\PrivateChannel con un nombre de App.Models.User.1. Por supuesto, además de devolver instancias de modelo Eloquent desde el método broadcastOn de tu modelo, puedes devolver instancias completas de Channel para tener un control total sobre los nombres de canal del modelo:

use Illuminate\Broadcasting\PrivateChannel;
 
/**
* Obtener los canales en los que se deben emitir eventos del modelo.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(string $event): array
{
return [
new PrivateChannel('user.'.$this->id)
];
}

Si planeas devolver explícitamente una instancia de canal desde el método broadcastOn de tu modelo, puedes pasar una instancia de modelo Eloquent al constructor del canal. Al hacerlo, Laravel usará las convenciones de canal de modelo discutidas anteriormente para convertir el modelo Eloquent en una cadena de nombre de canal:

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

Si necesitas determinar el nombre del canal de un modelo, puedes llamar al método broadcastChannel en cualquier instancia de modelo. Por ejemplo, este método devuelve la cadena App.Models.User.1 para un modelo App\Models\User con un id de 1:

$user->broadcastChannel()

Convenciones de Eventos

Dado que los eventos de difusión del modelo no están asociados a un evento "real" dentro del directorio App\Events de tu aplicación, se les asigna un nombre y una carga útil basados en convenciones. La convención de Laravel es emitir el evento usando el nombre de clase del modelo (sin incluir el espacio de nombres) y el nombre del evento del modelo que desencadenó la difusión.

Entonces, por ejemplo, una actualización del modelo App\Models\Post emitiría un evento a tu aplicación del lado del cliente como PostUpdated con la siguiente carga útil:

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

La eliminación del modelo App\Models\User emitiría un evento con el nombre UserDeleted.

Si lo deseas, puedes definir un nombre de difusión personalizado y una carga útil agregando un método broadcastAs y broadcastWith a tu modelo. Estos métodos reciben el nombre del evento/operación del modelo que está ocurriendo, lo que te permite personalizar el nombre del evento y la carga útil para cada operación del modelo. Si se devuelve null en el método broadcastAs, Laravel usará las convenciones de nombre de evento de difusión de modelo discutidas anteriormente al emitir el evento:

/**
* El nombre de la emisión del evento del modelo.
*/
public function broadcastAs(string $event): string|null
{
return match ($event) {
'created' => 'post.created',
default => null,
};
}
 
/**
* Obtener los datos para la emisión del modelo.
*
* @return array<string, mixed>
*/
public function broadcastWith(string $event): array
{
return match ($event) {
'created' => ['title' => $this->title],
default => ['model' => $this],
};
}

Escuchar Difusiones de Modelos

Una vez que hayas agregado el rasgo BroadcastsEvents a tu modelo y hayas definido el método broadcastOn de tu modelo, estás listo para comenzar a escuchar eventos de modelo difundidos en tu aplicación del lado del cliente. Antes de comenzar, es posible que desees consultar la documentación completa sobre escuchar eventos.

Primero, usa el método private para recuperar una instancia de un canal, luego llama al método listen para escuchar un evento específico. Típicamente, el nombre del canal dado al método private debería corresponder a las convenciones de difusión de modelos de Laravel.

Una vez que hayas obtenido una instancia de canal, puedes usar el método listen para escuchar un evento en particular. Dado que los eventos de difusión del modelo no están asociados a un evento "real" dentro del directorio App\Events de tu aplicación, el nombre del evento debe tener un prefijo . para indicar que no pertenece a un espacio de nombres en particular. Cada evento de difusión del modelo tiene una propiedad model que contiene todas las propiedades que se pueden difundir del modelo:

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

Eventos de Cliente

Nota Cuando usas Pusher Channels, debes habilitar la opción "Client Events" en la sección "App Settings" de tu panel de aplicación para poder enviar eventos del cliente.

A veces, es posible que desees difundir un evento a otros clientes conectados sin afectar a tu aplicación Laravel en absoluto. Esto puede ser particularmente útil para cosas como notificaciones de "escritura", donde deseas alertar a los usuarios de tu aplicación que otro usuario está escribiendo un mensaje en una pantalla dada.

Para difundir eventos del cliente, puedes usar el método whisper de Echo:

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

Para escuchar eventos del cliente, puedes usar el método listenForWhisper:

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

Notificaciones

Al combinar la difusión de eventos con notificaciones, tu aplicación JavaScript puede recibir nuevas notificaciones a medida que ocurren sin necesidad de actualizar la página. Antes de comenzar, asegúrate de leer la documentación sobre el uso del canal de notificación de difusión.

Una vez que hayas configurado una notificación para usar el canal de difusión, puedes escuchar los eventos de difusión usando el método notification de Echo. Recuerda, el nombre del canal debe coincidir con el nombre de la clase de la entidad que recibe las notificaciones:

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

En este ejemplo, todas las notificaciones enviadas a instancias de App\Models\User a través del canal broadcast serían recibidas por la devolución de llamada. Un callback de autorización de canal para el canal App.Models.User.{id} está incluido en el BroadcastServiceProvider predeterminado que se incluye con el framework Laravel.