[TOC]
## 1、簡介
Lumen事件提供了簡單的觀察者模式實現,允許你訂閱和監聽應用中的事件。事件類通常存放在`app/Events`目錄,監聽器存放在`app/Listeners`。
## 2、注冊事件/監聽器
Lumen自帶的`EventServiceProvider`為事件注冊提供了方便之所。其中的`listen`屬性包含了事件(鍵)和對應監聽器(值)數組。如果應用需要,你可以添加多個事件到該數組。例如,讓我們添加`PodcastWasPurchased`事件:
~~~
/**
* 事件監聽器映射
*
* @var array
*/
protected $listen = [
'App\Events\PodcastWasPurchased' => [
'App\Listeners\EmailPurchaseConfirmation',
],
];
~~~
## 3、定義事件
事件類是一個處理與事件相關的簡單數據容器,例如,假設我們生成的`PodcastWasPurchased`事件接收一個[Eloquent ORM](http://laravelacademy.org/post/138.html)對象:
~~~
<?php
namespace App\Events;
use App\Podcast;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
class PodcastWasPurchased extends Event{
use SerializesModels;
public $podcast;
/**
* 創建新的事件實例
*
* @param Podcast $podcast
* @return void
*/
public function __construct(Podcast $podcast)
{
$this->podcast = $podcast;
}
}
~~~
正如你所看到的,該事件類不包含任何特定邏輯,只是一個存放被購買的`Podcast`對象的容器,如果事件對象被序列化的話,事件使用的?`SerializesModels`?trait將會使用PHP的`serialize`函數序列化所有Eloquent模型。
## 4、定義監聽器
接下來,讓我們看看我們的示例事件的監聽器,事件監聽器在`handle`方法中接收事件實例。在`handle`方法內,你可以執行任何需要的邏輯以響應事件。
~~~
<?php
namespace App\Listeners;
use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class EmailPurchaseConfirmation{
/**
* 創建事件監聽器
*
* @return void
*/
public function __construct()
{
//
}
/**
* 處理事件
*
* @param PodcastWasPurchased $event
* @return void
*/
public function handle(PodcastWasPurchased $event)
{
// Access the podcast using $event->podcast...
}
}
~~~
你的事件監聽器還可以在構造器中類型提示任何需要的依賴,所有事件監聽器通過[服務容器](http://laravelacademy.org/post/471.html)解析,所以依賴會自動注入。
停止事件繼續往下傳播
有時候,你希望停止事件被傳播到其它監聽器,你可以通過從監聽器的`handle`方法中返回`false`來實現。
#### 4.1 事件監聽器隊列
需要將事件監聽器放到[隊列](http://laravelacademy.org/post/469.html)中?沒有比這更簡單的了,只需要讓監聽器類實現`ShouldQueue`接口即可,通過Artisan命令`event:generate`生成的監聽器類已經將接口導入當前命名空間,所有你可以立即拿來使用:
~~~
<?php
namespace App\Listeners;
use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class EmailPurchaseConfirmation implements ShouldQueue{
//
}
~~~
就是這么簡單,當監聽器被事件調用,將會使用Lumen的[隊列系統](http://laravelacademy.org/post/469.html)通過隊列分發器自動隊列化。如果通過隊列執行監聽器的時候沒有拋出任何異常,隊列任務在執行完成后被自動刪除。
手動訪問隊列
如果你需要手動訪問底層隊列任務的`delete`和`release`方法,在生成的監聽器中默認導入的`Illuminate\Queue\InteractsWithQueue`?trait提供了訪問這兩個方法的權限:
~~~
<?php
namespace App\Listeners;
use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class EmailPurchaseConfirmation implements ShouldQueue{
use InteractsWithQueue;
public function handle(PodcastWasPurchased $event)
{
if (true) {
$this->release(30);
}
}
}
~~~
## 5、觸發事件
要觸發一個事件,可以使用Event[門面](http://laravelacademy.org/post/97.html),傳遞一個事件實例到`fire`方法,`fire`方法會分發事件到所有監聽器:
~~~
<?php
namespace App\Http\Controllers;
use Event;
use App\Podcast;
use App\Events\PodcastWasPurchased;
use App\Http\Controllers\Controller;
class UserController extends Controller{
/**
* 顯示指定用戶屬性
*
* @param int $userId
* @param int $podcastId
* @return Response
*/
public function purchasePodcast($userId, $podcastId)
{
$podcast = Podcast::findOrFail($podcastId);
// Purchase podcast logic...
Event::fire(new PodcastWasPurchased($podcast));
}
}
~~~
此外,你還可以使用全局的幫助函數`event`來觸發事件:
~~~
event(new PodcastWasPurchased($podcast));
~~~
## 6、廣播
在很多現代web應用中,web套接字被用于實現實時更新的用戶接口。當一些數據在服務器上被更新,通常一條消息通過websocket連接被發送給客戶端處理。
為幫助你構建這樣的應用,Lumen讓通過websocket連接廣播事件變得簡單。廣播Lumen事件允許你在服務端和客戶端JavaScript框架之間共享同一事件名。
#### 6.1 配置
Lumen支持多種廣播驅動:[Pusher](https://pusher.com/)、[Redis](http://laravelacademy.org/tags/redis "View all posts in Redis")以及一個服務于本地開發和調試的日志驅動。每一個驅動都有一個配置示例。BROADCAST_DRIVER配置選項可用于設置默認驅動。
廣播預備知識
事件廣播需要以下兩個依賴:
* [Pusher](http://laravelacademy.org/tags/pusher "View all posts in Pusher"):?`pusher/pusher-php-server ~2.0`
* [Redis](http://laravelacademy.org/tags/redis "View all posts in Redis"):?`predis/predis ~1.0`
隊列預備知識
在開始介紹廣播事件之前,還需要配置并運行一個[隊列監聽器](http://laravelacademy.org/post/469.html)。所有事件廣播都通過隊列任務來完成以便應用的響應時間不受影響。
#### 6.2 將事件標記為廣播
要告訴Lumen給定事件應該被廣播,需要在事件類上實現`Illuminate\Contracts\Broadcasting\ShouldBroadcast`接口。`ShouldBroadcast`接口要求你實現一個方法:`broadcastOn`。該方法應該返回事件廣播”頻道“名稱數組:
~~~
<?php
namespace App\Events;
use App\User;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class ServerCreated extends Event implements ShouldBroadcast{
use SerializesModels;
public $user;
/**
* 創建新的事件實例
*
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* 獲取事件廣播頻道
*
* @return array
*/
public function broadcastOn()
{
return ['user.'.$this->user->id];
}
}
~~~
然后,你只需要和正常一樣[觸發該事件](http://laravelacademy.org/post/467.html#ipt_kb_toc_467_5),事件被觸發后,一個[隊列任務](http://laravelacademy.org/post/469.html)將通過指定廣播驅動自動廣播該事件。
#### 6.3 廣播數據
如果某個事件被廣播,其所有的`public`屬性都會按照事件負載自動序列化和廣播,從而允許你從JavaScript中訪問所有`public`數據,因此,舉個例子,如果你的事件有一個單獨的包含Eloquent模型的`$user`屬性,廣播負載定義如下:
~~~
{
"user": {
"id": 1,
"name": "Jonathan Banks"
...
}
}
~~~
然而,如果你希望對廣播負載有更加細粒度的控制,可以添加`broadcastWith`方法到事件,該方法應該返回你想要通過事件廣播的數組數據:
~~~
/**
* 獲取廣播數據
*
* @return array
*/
public function broadcastWith(){
return ['user' => $this->user->id];
}
~~~
#### 6.4 消費事件廣播
Pusher
你可以通過Pusher的JavaScript SDK方便地使用[Pusher](https://pusher.com/)驅動消費事件廣播。例如,讓我們從之前的例子中消費App\Events\ServerCreated事件:
~~~
this.pusher = new Pusher('pusher-key');
this.pusherChannel = this.pusher.subscribe('user.' + USER_ID);
this.pusherChannel.bind('App\\Events\\ServerCreated', function(message) {
console.log(message.user);
});
~~~
Redis
如果你在使用Redis廣播,你將需要編寫自己的Redis pub/sub消費者來接收消息并使用自己選擇的websocket技術將其進行廣播。例如,你可以選擇使用使用Node編寫的流行的[Socket.io](http://socket.io/)庫。
使用Node庫`socket.io`和`ioredis`,你可以快速編寫事件廣播發布所有廣播事件:
~~~
var app = require('http').createServer(handler);
var io = require('socket.io')(app);
var Redis = require('ioredis');
var redis = new Redis();
app.listen(6001, function() {
console.log('Server is running!');});
function handler(req, res) {
res.writeHead(200);
res.end('');}
io.on('connection', function(socket) {
//
});
redis.psubscribe('*', function(err, count) {
//
});
redis.on('pmessage', function(subscribed, channel, message) {
message = JSON.parse(message);
~~~
});
##7、事件訂閱者
事件訂閱者是指那些在類本身中訂閱到多個事件的類,從而允許你在單個類中定義一些事件處理器。訂閱者應該定義一個`subscribe`方法,該方法中傳入一個事件分發器實例:
~~~
<?php
namespace App\Listeners;
class UserEventListener{
/**
* 處理用戶登錄事件
*/
public function onUserLogin($event) {}
/**
* 處理用戶退出事件
*/
public function onUserLogout($event) {}
/**
* 為訂閱者注冊監聽器
*
* @param Illuminate\Events\Dispatcher $events
* @return array
*/
public function subscribe($events)
{
$events->listen(
'App\Events\UserLoggedIn',
'App\Listeners\UserEventListener@onUserLogin'
);
$events->listen(
'App\Events\UserLoggedOut',
'App\Listeners\UserEventListener@onUserLogout'
);
}
}
~~~
#### 7.1 注冊一個事件訂閱者
訂閱者被定義后,可以通過事件分發器進行注冊,你可以使用`EventServiceProvider`上的`$subcribe`屬性來注冊訂閱者。例如,讓我們添加`UserEventListener`:
~~~
<?php
namespace App\Providers;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider{
/**
* 事件監聽器映射數組
*
* @var array
*/
protected $listen = [
//
];
/**
* 要注冊的訂閱者
*
* @var array
*/
protected $subscribe = [
'App\Listeners\UserEventListener',
];
}
~~~