Advanced Laravel Broadcasting: Ứng dụng Real-Time
Laravel Broadcasting cho phép bạn broadcast dữ liệu đến các connected clients một cách real-time. Kết hợp với Reverb (WebSocket server của Laravel), bạn có thể xây dựng ứng dụng chat, live notifications, các công cụ cộng tác, và nhiều hơn nữa.
Setup
Cài đặt Reverb:
composer require laravel/reverb
php artisan reverb:install
Cấu hình trong .env:
REVERB_APP_ID=your-app-id
REVERB_APP_KEY=your-app-key
REVERB_APP_SECRET=your-app-secret
REVERB_HOST=localhost
REVERB_PORT=8080
Khởi chạy Reverb:
php artisan reverb:start
Basic Broadcasting Event
Tạo một broadcastable event:
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithBroadcasting;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class MessageSent implements ShouldBroadcast
{
use Dispatchable, InteractsWithBroadcasting, SerializesModels;
public function __construct(
public Message $message,
) {}
public function broadcastOn(): array
{
return [
new Channel('chat.' . $this->message->conversation_id),
];
}
public function broadcastAs(): string
{
return 'message.sent';
}
public function broadcastWith(): array
{
return [
'id' => $this->message->id,
'text' => $this->message->text,
'user' => $this->message->user->only(['id', 'name']),
];
}
}
Listen on Frontend
// resources/js/chat.js
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT,
wssPort: import.meta.env.VITE_REVERB_PORT,
forceTLS: import.meta.env.VITE_REVERB_SCHEME === 'https',
enabledTransports: ['ws', 'wss'],
});
// Listen for messages
window.Echo.channel(`chat.${conversationId}`)
.listen('message.sent', (data) => {
console.log('New message:', data);
addMessageToUI(data);
});
Private Channels
Hạn chế truy cập cho những người dùng cụ thể:
// routes/channels.php
use App\Models\User;
use App\Models\Conversation;
Broadcast::channel('conversation.{id}', function (User $user, int $id) {
return $user->conversations()->where('id', $id)->exists();
});
Broadcasting đến private channels:
class MessageSent implements ShouldBroadcast
{
public function broadcastOn(): array
{
return [
new PrivateChannel('conversation.' . $this->message->conversation_id),
];
}
}
Listen on frontend:
window.Echo.private(`conversation.${conversationId}`)
.listen('message.sent', (data) => {
addMessageToUI(data);
});
Presence Channels
Theo dõi người dùng nào đang online và đang xem:
// routes/channels.php
Broadcast::channel('conversation.{id}:presence', function (User $user, int $id) {
return $user->conversations()->where('id', $id)->exists()
? ['id' => $user->id, 'name' => $user->name]
: false;
});
Broadcasting đến presence channel:
class UserTyping implements ShouldBroadcast
{
public function broadcastOn(): array
{
return [
new PresenceChannel('conversation.' . $this->conversationId . ':presence'),
];
}
public function broadcastWith(): array
{
return [
'user_id' => $this->user->id,
'user_name' => $this->user->name,
];
}
}
Listen on frontend:
window.Echo.join(`conversation.${conversationId}:presence`)
.here((users) => {
console.log('Currently online:', users);
})
.joining((user) => {
console.log(user.name + ' joined');
})
.leaving((user) => {
console.log(user.name + ' left');
})
.listen('UserTyping', (data) => {
showTypingIndicator(data.user_name);
});
Real-Time Notifications
class OrderShipped implements ShouldBroadcast
{
public function __construct(
public Order $order,
) {}
public function broadcastOn(): array
{
return [
new PrivateChannel('user.' . $this->order->user_id),
];
}
public function broadcastAs(): string
{
return 'order.shipped';
}
public function broadcastWith(): array
{
return [
'order_id' => $this->order->id,
'tracking_number' => $this->order->tracking_number,
];
}
}
Frontend:
window.Echo.private(`user.${userId}`)
.notification((notification) => {
console.log('Notification:', notification);
})
.listen('order.shipped', (data) => {
showNotification(`Order #${data.order_id} has shipped!`);
});
Debouncing Broadcasts
Ngăn chặn quá nhiều events:
class UserSearching implements ShouldBroadcast
{
public function __construct(
public string $query,
public int $userId,
) {}
public function broadcastOn(): array
{
return [new Channel('search')];
}
}
// Controller
public function search(Request $request)
{
event(new UserSearching($request->query, auth()->id()));
return SearchService::find($request->query);
}
Frontend với debounce:
import { debounce } from 'lodash-es';
const search = debounce((query) => {
window.Echo.channel('search')
.listen('UserSearching', (data) => {
updateSearchResults(data);
});
}, 300);
document.getElementById('search').addEventListener('input', (e) => {
search(e.target.value);
});
Conditional Broadcasting
Chỉ broadcast dưới các điều kiện nhất định:
class PostPublished implements ShouldBroadcast
{
public function __construct(public Post $post) {}
public function broadcastOn(): array
{
return $this->post->is_public
? [new Channel('posts')]
: [];
}
}
Hoặc kiểm tra on the fly:
public function broadcastWhen(): bool
{
return $this->post->is_published && !$this->post->is_scheduled;
}
Broadcasting Model Changes
class Post extends Model
{
protected $dispatchesEvents = [
'created' => PostCreated::class,
'updated' => PostUpdated::class,
'deleted' => PostDeleted::class,
];
}
class PostUpdated implements ShouldBroadcast
{
public function __construct(public Post $post) {}
public function broadcastOn(): array
{
return [new PrivateChannel('posts.' . $this->post->user_id)];
}
public function broadcastWith(): array
{
return $this->post->only(['id', 'title', 'content', 'updated_at']);
}
}
Testing Broadcasting
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\Facades\Event;
public function test_message_is_broadcast()
{
Event::fake();
$message = Message::factory()->create();
event(new MessageSent($message));
Event::assertDispatched(MessageSent::class);
}
Performance Optimization
Queue Broadcasting
Ngăn chặn blocking requests trong broadcast:
class MessageSent implements ShouldBroadcast, ShouldQueue
{
use Dispatchable, InteractsWithBroadcasting;
public $queue = 'broadcasts';
}
Compress Data
Chỉ broadcast những gì cần thiết:
public function broadcastWith(): array
{
return [
'id' => $this->message->id,
'text' => $this->message->text,
'user_id' => $this->message->user->id,
// Không include heavy relationships
];
}
Batch Broadcasting
public function broadcastMultiple(array $userIds)
{
foreach ($userIds as $userId) {
event(new Notification($userId, $data));
}
// Hoặc sử dụng batch
Bus::batch(array_map(
fn($id) => new BroadcastNotification($id, $data),
$userIds
))->dispatch();
}
Best Practices
- Validate channel access - Luôn kiểm tra authorization
- Compress data - Không broadcast huge payloads
- Use queues - Broadcast một cách asynchronous
- Limit listeners - Không broadcast cho mọi người
- Handle reconnections - Client-side retry logic
- Monitor connections - Theo dõi active users
- Test thoroughly - Test broadcasting logic
Common Patterns
Live Comments
class CommentPosted implements ShouldBroadcast
{
public function broadcastOn()
{
return new PrivateChannel('post.' . $this->comment->post_id);
}
}
Live Notifications
class Notification implements ShouldBroadcast, ShouldQueue
{
public function broadcastOn()
{
return new PrivateChannel('user.' . $this->user_id);
}
}
Collaboration
class DocumentUpdated implements ShouldBroadcast
{
public function broadcastOn()
{
return new PrivateChannel('document.' . $this->document_id);
}
}
Tóm tắt
Laravel Broadcasting với Reverb cho phép các tính năng real-time:
- Public channels - Broadcast cho mọi người
- Private channels - Hạn chế cho những người dùng cụ thể
- Presence channels - Theo dõi những người dùng online
- Queue broadcasting - Non-blocking broadcasts
- Model events - Auto-broadcast on changes
- Client-side listening - JavaScript integration
Xây dựng ứng dụng responsive, real-time một cách dễ dàng.