Broadcasting

Send a server-side event to the browser over a WebSocket, with private channels and an authorisation callback.

Syntaxpublic function broadcastOn(): array

Broadcasting pushes an event you already dispatch on the server out to a JavaScript client over a WebSocket, so a page updates without polling. Install it with php artisan install:broadcasting, which writes config/broadcasting.php and routes/channels.php and sets up Laravel Echo in the front end. Choose the connection with BROADCAST_CONNECTION in .env: reverb, pusher, ably or mercure.

What makes an event broadcast

Add the Illuminate\Contracts\Broadcasting\ShouldBroadcast interface and a broadcastOn(): array method that returns channel objects. On the wire the event is named after its class unless broadcastAs() returns another name, and the payload is every public property unless broadcastWith(): array narrows it. ShouldBroadcast queues the broadcast; ShouldBroadcastNow sends it inside the request.

Channels and authorisation

Channel is public. PrivateChannel and PresenceChannel make Echo post to /broadcasting/auth first, and that request runs the matching callback in routes/channels.php. A private callback returns a boolean. A presence callback returns an array of user data instead, and that array is what other members receive through here(), joining() and leaving(). A {order} placeholder in the channel name is resolved by route model binding, so the callback receives the model, not the id.

Example

Example · php
<?php
// app/Events/OrderShipped.php

declare(strict_types=1);

namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderShipped implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(
        public Order $order,
    ) {}

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

    public function broadcastAs(): string
    {
        return 'order.shipped';
    }

    /**
     * @return array<string, mixed>
     */
    public function broadcastWith(): array
    {
        return [
            'id' => $this->order->id,
            'status' => $this->order->status,
        ];
    }
}

When to use it

  • An order tracking page that moves from paid to shipped without the customer reloading it.
  • A chat room that shows who is currently present, using a presence channel's member list.
  • An admin dashboard whose counters move as records are created elsewhere in the system.
  • A long import that reports progress to the browser from the worker running it.

More examples

Authorising private and presence channels

The channel name in broadcastOn() must match the pattern here, or the subscription is refused with a 403 from /broadcasting/auth and the event appears to vanish. The callback runs through the authentication guards, so an unauthenticated visitor never reaches it.

Example · php
<?php
// routes/channels.php

use App\Models\Order;
use App\Models\Room;
use App\Models\User;
use Illuminate\Support\Facades\Broadcast;

// {order} is resolved by route model binding before the callback runs.
Broadcast::channel('orders.{order}', function (User $user, Order $order): bool {
    return $user->id === $order->user_id;
});

// A presence callback returns the data other members will see, not a boolean.
Broadcast::channel('rooms.{room}', function (User $user, Room $room): ?array {
    if (! $user->canJoin($room)) {
        return null;
    }

    return ['id' => $user->id, 'name' => $user->name];
});

Listening in the browser with Echo

Note the leading dot in '.order.shipped'. Without broadcastAs() you listen for the class name, 'App\\Events\\OrderShipped'; once broadcastAs() returns a custom name, the dot tells Echo not to prepend the application namespace. A missing dot is the usual reason a correct-looking listener never fires.

Example · javascript
// resources/js/app.js

// Private channel: Echo posts to /broadcasting/auth before subscribing.
window.Echo.private(`orders.${orderId}`)
    .listen('.order.shipped', (payload) => {
        // payload is what broadcastWith() returned.
        console.log(payload.id, payload.status);
    });

// Presence channel: the array from the auth callback arrives here.
window.Echo.join(`rooms.${roomId}`)
    .here((members) => console.log(members))
    .joining((member) => console.log(member.name))
    .leaving((member) => console.log(member.name));

Excluding the browser that caused it

toOthers() needs the InteractsWithSockets trait on the event and the X-Socket-ID header that Echo adds to requests. Use it when the page has already drawn the change locally, so the broadcast would otherwise add the same row twice.

Example · php
use App\Events\OrderShipped;

// Everyone on the channel, including the user whose request caused it:
OrderShipped::dispatch($order);

// Everyone except that user's own connection:
broadcast(new OrderShipped($order))->toOthers();

Discussion

  • Be the first to comment on this lesson.