Laravel Echo 是 5.3 版推出來的新功能。廣播必需透過 佇列(queue) 的功能,所以在 Window 系統就直接 GG 了。這邊我改用 Cloud9 開出 liunx PHP 環境來實驗,如果你有 cloud9 的帳號,可以從這邊 直接 clone 一份 自行研究。Cloud9 的使用可以參考先前文章 Cloud9 with Laravel 5.3,這次實驗參考了 Introducing Laravel Echo: An In-Depth Walk-Through 文章來跟著作,並記錄一些概念上筆記。
驅動
Laravel Echo 目前支援 pusher
, redis
, log
, null
驅動模式,可以從 config/broadcasting.php
去設定,但不建議這樣做,改由定義 .env
中的 BROADCAST_DRIVER
等相關的環境變數是會比較好的作法。
Laravel 大部分設定檔,都應該先從 .env 環境變數去設定
這次使用 pusher 的方式,但下面還是會說明一下各驅動差異:
- pusher 模式
如果使用的是 pusher 模式,Laravel 會將事件包內容先發送第三方推播服務平台 pusher.com 上,透過 pusher 後台的 Debug Console 可以看見下圖:
而客端程式則透過 laravel-echo 設定 pusher app key 來連線平台,取得平台所發送的事件。(必需先在 pusher.com 申請一個帳號並建立一個 APP)
1 2 3 4 5
| var Echo = require('laravel-echo'); window.Echo = new Echo({ broadcaster: 'pusher', key: '244083293b984a086228' });
|
- redis 模式
在使用 redis 模式之前,必需先 安裝 redis-server 並啟動,如果使用的是 cloud9 參考 Setting Up Redis 。Laravel 將事件包內容寫入至 redis 資料庫,透過 node.js 的 ioredis 捕捉 redis 的 Pub/Sub 事件,然後再用 WebSocket 或 Socket.io 再轉發一次事件至客端。
範例程式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| var io = require('socket.io')(3000); var Redis = require('ioredis'); var redis = new Redis();
redis.subscribe('chat-room.9453', function(err, count) { console.log('redis connect!'); });
redis.on('message', function(channel, msg) { console.log('頻道名稱:', channel); console.log('資料內容', msg);
io.emit(msg.event, msg.data); });
|
不過這種自刻的簡單 Server,是沒辦法使用下列官方的這個作法
1 2 3 4 5
| var EchoSocket = require('laravel-echo'); window.EchoSocket = new Echo({ broadcaster: 'socket.io', host: 'http://127.0.0.1:3000' });
|
因為並沒有實作對應的 laravel-echo 的函式的處理部分,若要使用的話,可以使用官方建議的 laravel-echo-server 來做 Node.js 的 Echo Server。換句話說,你可以想成 laravel-echo-server 就是自架 pusher server 的感覺。
- log 模式
如果使用 log 的話,Laravel 僅是將事件包的資料寫入至記錄檔 storage/logs/laravel.log
中,內容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| [2016-09-06 03:32:14] local.INFO: Broadcasting [App\Events\ChatMessageWasReceived] on channels [pub-room.9453, pri-room.1, pri-room.2, pre-room.1] with payload: { "chatMessage": { "id": 25, "message": "ABC", "user_id": "1", }, "user": { "id": 1, "name": "Patrick Stewart" } "socket": null }
|
這樣方式通常用於 debug 的時候,檢查伺服發送事件包頻道以及內容是否正確
- null 模式
當驅動模式為 null 時,表示關閉廣播功能
事件
在這邊我稱它為事件包(封包),是整個廣播系統的主角。發送到頻道的事件包類別,都必需實作 ShouldBroadcast
介面才行,通常會放至 app/Events 資料夾(在 v5.3 此資料夾已被移除了,相對的原本在 5.2 中抽象類別 Event 也不存在,若需要再自行建立並繼承)
1 2 3 4 5 6
| class ChatMessageWasReceived implements ShouldBroadcast{ use InteractsWithSockets, SerializesModels;
public $user; }
|
所有事件包中的公開屬性,在傳送時將會自動序列化成資料如下
1 2 3 4 5 6
| { "user": { "id": 1, "name": "Patrick Stewart" } }
|
但假設你希望的資料是這樣子
1 2 3 4
| { "id": 1, "created_at": "2016-09-05 12:11:10" }
|
可以透過追加定義 broadcastWith
重新組合的資料格式或追加其它所需資料
1 2 3 4 5 6 7
| public function broadcastWith() { return [ 'id' => $this->user->name, 'created_at' => date('Y-m-d H:i:s') ]; }
|
而 broadcastOn
定義了當事件被送出時,會發送至哪些頻道上,如果打算一次發送至不同的頻道,可以使用陣列方式回傳如下:
1 2 3 4 5 6 7 8 9
| public function broadcastOn() { return [ "pub-room.9453", new PrivateChannel("pri-room.1"), new PrivateChannel("pri-room.2"), new PresenceChannel("pre-room.1"), ]; }
|
除了事件包外,也可以發送 通知(Notifications) 至頻道中
頻道
Laravel Echo 的頻道有「公開頻道」、「私有頻道」、「既存頻道」三種
公開頻道
- 所有使用者(包含未登入訪客),都可以接收此頻道事件
客端範例程式:
1 2 3 4 5 6 7 8 9
| Echo.channel('chat-room.9453') .listen('ChatMessageWasReceived', function(data) { console.log('收到 ChatMessageWasReceived 事件包'); }) .notification(function(notification) { console.log('通知事件', notification.type); });
|
私有頻道
- 接收頻道事件的使用者,需要先驗證(要求登入)
- 此頻道的使用者不會知道有其它使用者的存在
客端範例程式:
1 2 3 4 5 6 7 8 9 10
| Echo.private('live-room.999') .listen('ChatMessageWasReceived', function(data) { console.log('收到 ChatMessageWasReceived 事件包'); console.log( data.user, data.chatMessage); }) .notification(function(notification) { console.log('通知事件', notification.type); });
|
既存頻道
- 接收頻道事件的使用者,需要先驗證(要求登入)
- 當使用者加入或離開此頻道時,會先觸發
here
, joining
, leaving
等事件,所以使用者會知道其它使用者的存在
客端範例程式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| Echo.join('chat-room.1') .here(function (members) { console.log('目前此頻道所有使用者有:'); console.table(members); }) .joining(function (joiningMember, members) { console.log('有一位使用者加入了頻道') console.table(joiningMember); }) .leaving(function (leavingMember, members) { console.log('有一位使用者離開了頻道') console.table(leavingMember); }) .listen('ChatMessageWasReceived', function(data) { console.log('收到 ChatMessageWasReceived 事件包'); console.log( data.user, data.chatMessage); }) .notification(function(notification) { console.log('通知事件', notification.type); });
|
驗證
「私有頻道」和「既存頻道」基本都會先檢查使用者是否已經登入,接著若需要進階驗證判斷權限之類(例如某個使用者是否能進入某個聊天室)的,則需要在 app/Providers/BroadcastServiceProvider.php
自行定義,記得要先到 config/app.php 啟用(把 App\Providers\BroadcastServiceProvider::class,
的註解拿掉)。在這邊不會去定義公開頻道(因為不需要驗證)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public function boot() { Broadcast::routes();
Broadcast::channel('App.User.*', function ($user, $userId) { return (int) $user->id === (int) $userId; });
Broadcast::channel('chat-room.*', function ($user, $chatroomId) { $userCanEntry = $user->checkCanEntry($chatroomId); if (userCanEntry) { return [ 'id' => $user->id, 'name' => $user->name, 'age' => $user->age ]; } else { return false } }); }
|