About us Guides Projects Contacts
Админка
please wait

Laravel's event broadcasting system provides real-time capabilities through WebSockets. While Pusher is the default driver, you can implement custom broadcasters for services like PubNub, Ably, or your own WebSocket server. This guide covers implementing custom broadcasting from a senior developer's perspective.

Why Custom Broadcasting

Custom broadcasters enable:

  1. Alternative Services: Use PubNub, Ably, or self-hosted solutions
  2. Cost Control: Avoid Pusher pricing for high-volume apps
  3. Specific Features: Leverage unique provider capabilities
  4. Compliance: Meet data residency requirements
  5. Integration: Connect with existing infrastructure

Laravel Broadcasting Basics

Configure Broadcasting

config/broadcasting.php:

<?php
return [
'default' => env('BROADCAST_DRIVER', 'null'),
'connections' => [
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => true,
],
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
],
// Custom broadcaster
'pubnub' => [
'driver' => 'pubnub',
'publish_key' => env('PUBNUB_PUBLISH_KEY'),
'subscribe_key' => env('PUBNUB_SUBSCRIBE_KEY'),
'secret_key' => env('PUBNUB_SECRET_KEY'),
'server_uuid' => env('PUBNUB_SERVER_UUID'),
],
],
];

Environment Configuration

.env:

BROADCAST_DRIVER=pubnub
PUBNUB_PUBLISH_KEY=pub-c-xxxxx
PUBNUB_SUBSCRIBE_KEY=sub-c-xxxxx
PUBNUB_SECRET_KEY=sec-c-xxxxx
PUBNUB_SERVER_UUID=server-unique-id

Creating a Custom Broadcaster

Install Provider SDK

composer require pubnub/pubnub

Create Broadcaster Class

app/Broadcasting/PubNubBroadcaster.php:

<?php
namespace App\Broadcasting;
use Illuminate\Broadcasting\Broadcasters\Broadcaster;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use PubNub\PNConfiguration;
use PubNub\PubNub;
use PubNub\Exceptions\PubNubException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class PubNubBroadcaster extends Broadcaster
{
protected PubNub $pubnub;
protected array $config;
public function __construct(array $config)
{
$this->config = $config;
$pnConfig = new PNConfiguration();
$pnConfig->setPublishKey($config['publish_key']);
$pnConfig->setSubscribeKey($config['subscribe_key']);
$pnConfig->setSecretKey($config['secret_key']);
$pnConfig->setUuid($config['server_uuid']);
$this->pubnub = new PubNub($pnConfig);
}
/**
* Authenticate the incoming request for a given channel.
*/
public function auth($request)
{
$channelName = $request->channel_name;
// Handle private and presence channels
if (Str::startsWith($channelName, ['private-', 'presence-'])) {
return $this->verifyUserCanAccessChannel(
$request,
$channelName
);
}
// Public channels don't require auth
return true;
}
/**
* Return the valid authentication response.
*/
public function validAuthenticationResponse($request, $result)
{
if (Str::startsWith($request->channel_name, 'presence-')) {
return [
'channel' => $request->channel_name,
'auth_key' => $this->generateAuthKey($request->channel_name),
'data' => [
'user_id' => $result['user_id'] ?? null,
'user_info' => $result['user_info'] ?? [],
],
];
}
return [
'channel' => $request->channel_name,
'auth_key' => $this->generateAuthKey($request->channel_name),
];
}
/**
* Broadcast the given event.
*/
public function broadcast(array $channels, $event, array $payload = [])
{
$socket = Arr::pull($payload, 'socket');
// Add event name to payload
$payload['event'] = class_basename($event);
try {
foreach ($this->formatChannels($channels) as $channel) {
$this->pubnub->publish()
->channel($channel)
->message($payload)
->sync();
}
} catch (PubNubException $e) {
throw new BroadcastException(
sprintf('PubNub error: %s', $e->getMessage())
);
}
}
/**
* Get the PubNub SDK instance.
*/
public function getPubNub(): PubNub
{
return $this->pubnub;
}
/**
* Generate an authentication key for the channel.
*/
protected function generateAuthKey(string $channel): string
{
return hash_hmac('sha256', $channel, $this->config['secret_key']);
}
/**
* Verify the user can access the channel.
*/
protected function verifyUserCanAccessChannel($request, string $channelName)
{
$channelName = Str::startsWith($channelName, 'private-')
? Str::replaceFirst('private-', '', $channelName)
: Str::replaceFirst('presence-', '', $channelName);
return parent::verifyUserCanAccessChannel(
$request,
$channelName
);
}
}

Register the Broadcaster

app/Providers/BroadcastServiceProvider.php:

<?php
namespace App\Providers;
use App\Broadcasting\PubNubBroadcaster;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider
{
public function boot()
{
// Register custom broadcaster
Broadcast::extend('pubnub', function ($app, $config) {
return new PubNubBroadcaster($config);
});
// Only register routes for drivers that need them
if (config('broadcasting.default') !== 'pubnub') {
Broadcast::routes();
}
require base_path('routes/channels.php');
}
}

Broadcasting Events

Create Broadcastable Event

<?php
namespace App\Events;
use App\Models\Message;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class MessageSent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public Message $message;
public function __construct(Message $message)
{
$this->message = $message;
}
/**
* Get the channels the event should broadcast on.
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('chat.' . $this->message->room_id),
];
}
/**
* The event's broadcast name.
*/
public function broadcastAs(): string
{
return 'message.sent';
}
/**
* Get the data to broadcast.
*/
public function broadcastWith(): array
{
return [
'id' => $this->message->id,
'content' => $this->message->content,
'user' => [
'id' => $this->message->user->id,
'name' => $this->message->user->name,
],
'created_at' => $this->message->created_at->toISOString(),
];
}
}

Define Channel Authorization

routes/channels.php:

<?php
use Illuminate\Support\Facades\Broadcast;
// Private channel - simple authorization
Broadcast::channel('chat.{roomId}', function ($user, $roomId) {
return $user->rooms()->where('id', $roomId)->exists();
});
// Presence channel - return user data
Broadcast::channel('room.{roomId}', function ($user, $roomId) {
if ($user->rooms()->where('id', $roomId)->exists()) {
return [
'id' => $user->id,
'name' => $user->name,
'avatar' => $user->avatar_url,
];
}
return false;
});
// Public channel (no auth needed)
Broadcast::channel('updates', function () {
return true;
});

Dispatch Events

// In controller or service
use App\Events\MessageSent;
public function sendMessage(Request $request, Room $room)
{
$message = $room->messages()->create([
'user_id' => auth()->id(),
'content' => $request->content,
]);
// Broadcast immediately
broadcast(new MessageSent($message));
// Or broadcast to others (excluding sender)
broadcast(new MessageSent($message))->toOthers();
return response()->json($message);
}

Frontend Integration

PubNub JavaScript Client

import PubNub from 'pubnub';
const pubnub = new PubNub({
publishKey: process.env.PUBNUB_PUBLISH_KEY,
subscribeKey: process.env.PUBNUB_SUBSCRIBE_KEY,
uuid: 'user-' + userId,
});
// Subscribe to channel
pubnub.subscribe({
channels: ['private-chat.1'],
});
// Listen for messages
pubnub.addListener({
message: (event) => {
const { channel, message } = event;
console.log('Received:', message);
// Handle based on event type
if (message.event === 'message.sent') {
addMessageToChat(message);
}
},
presence: (event) => {
console.log('Presence:', event.action, event.uuid);
},
});
// Publish message (usually done via the Laravel API instead)
pubnub.publish({
channel: 'private-chat.1',
message: { content: 'Hello!' },
});

Vue.js Component Example

<template>
<div class="chat">
<div class="messages">
<div v-for="msg in messages" :key="msg.id" class="message">
<strong>{{ msg.user.name }}:</strong>
{{ msg.content }}
</div>
</div>
<form @submit.prevent="sendMessage">
<input v-model="newMessage" placeholder="Type a message..." />
<button type="submit">Send</button>
</form>
</div>
</template>
<script>
import PubNub from 'pubnub';
export default {
props: ['roomId', 'user'],
data() {
return {
messages: [],
newMessage: '',
pubnub: null,
};
},
created() {
this.initPubNub();
this.fetchMessages();
},
beforeDestroy() {
this.pubnub.unsubscribe({ channels: [this.channelName] });
},
computed: {
channelName() {
return `private-chat.${this.roomId}`;
},
},
methods: {
initPubNub() {
this.pubnub = new PubNub({
subscribeKey: process.env.VUE_APP_PUBNUB_SUBSCRIBE_KEY,
uuid: `user-${this.user.id}`,
});
this.pubnub.addListener({
message: (event) => {
if (event.message.event === 'message.sent') {
this.messages.push(event.message);
}
},
});
this.pubnub.subscribe({ channels: [this.channelName] });
},
async fetchMessages() {
const response = await fetch(`/api/rooms/${this.roomId}/messages`);
this.messages = await response.json();
},
async sendMessage() {
if (!this.newMessage.trim()) return;
await fetch(`/api/rooms/${this.roomId}/messages`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
},
body: JSON.stringify({ content: this.newMessage }),
});
this.newMessage = '';
},
},
};
</script>

Using Laravel WebSockets

For self-hosted WebSocket servers:

composer require beyondcode/laravel-websockets
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider"
php artisan migrate

Configure config/websockets.php and start:

php artisan websockets:serve

Testing Broadcasting

<?php
namespace Tests\Feature;
use App\Events\MessageSent;
use App\Models\Message;
use App\Models\User;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;
class BroadcastingTest extends TestCase
{
public function test_message_sent_event_is_broadcast()
{
Event::fake();
$user = User::factory()->create();
$message = Message::factory()->create(['user_id' => $user->id]);
broadcast(new MessageSent($message));
Event::assertDispatched(MessageSent::class, function ($event) use ($message) {
return $event->message->id === $message->id;
});
}
public function test_broadcast_data_format()
{
$message = Message::factory()->create();
$event = new MessageSent($message);
$data = $event->broadcastWith();
$this->assertArrayHasKey('id', $data);
$this->assertArrayHasKey('content', $data);
$this->assertArrayHasKey('user', $data);
}
}

Key Takeaways

  1. Extend Broadcaster: Implement the Broadcaster contract
  2. Register in service provider: Use Broadcast::extend()
  3. Handle auth properly: Implement channel authorization
  4. Use broadcastWith(): Control what data is sent
  5. Test with Event::fake(): Verify events are dispatched
  6. Consider queuing: Use ShouldBroadcastNow for immediate delivery

Custom broadcasters give you the flexibility to use any real-time service while maintaining Laravel's elegant event-driven architecture.

 
 
 
Языки
Темы
Copyright © 1999 — 2026
ZK Interactive