Lumen事件提供了簡單的觀察者模式實(shí)現(xiàn),允許你訂閱和監(jiān)聽?wèi)?yīng)用中的事件。事件類通常存放在app/Events
目錄,監(jiān)聽器存放在app/Listeners
。
Lumen自帶的EventServiceProvider
為事件注冊提供了方便之所。其中的listen
屬性包含了事件(鍵)和對應(yīng)監(jiān)聽器(值)數(shù)組。如果應(yīng)用需要,你可以添加多個(gè)事件到該數(shù)組。例如,讓我們添加PodcastWasPurchased
事件:
/**
* 事件監(jiān)聽器映射
*
* @var array
*/
protected $listen = [
'App\Events\PodcastWasPurchased' => [
'App\Listeners\EmailPurchaseConfirmation',
],
];
事件類是一個(gè)處理與事件相關(guān)的簡單數(shù)據(jù)容器,例如,假設(shè)我們生成的PodcastWasPurchased
事件接收一個(gè)Eloquent ORM對象:
<?php
namespace App\Events;
use App\Podcast;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
class PodcastWasPurchased extends Event{
use SerializesModels;
public $podcast;
/**
* 創(chuàng)建新的事件實(shí)例
*
* @param Podcast $podcast
* @return void
*/
public function __construct(Podcast $podcast)
{
$this->podcast = $podcast;
}
}
正如你所看到的,該事件類不包含任何特定邏輯,只是一個(gè)存放被購買的Podcast
對象的容器,如果事件對象被序列化的話,事件使用的 SerializesModels
trait將會使用PHP的serialize
函數(shù)序列化所有Eloquent模型。
接下來,讓我們看看我們的示例事件的監(jiān)聽器,事件監(jiān)聽器在handle
方法中接收事件實(shí)例。在handle
方法內(nèi),你可以執(zhí)行任何需要的邏輯以響應(yīng)事件。
<?php
namespace App\Listeners;
use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class EmailPurchaseConfirmation{
/**
* 創(chuàng)建事件監(jiān)聽器
*
* @return void
*/
public function __construct()
{
//
}
/**
* 處理事件
*
* @param PodcastWasPurchased $event
* @return void
*/
public function handle(PodcastWasPurchased $event)
{
// Access the podcast using $event->podcast...
}
}
你的事件監(jiān)聽器還可以在構(gòu)造器中類型提示任何需要的依賴,所有事件監(jiān)聽器通過服務(wù)容器解析,所以依賴會自動注入。
停止事件繼續(xù)往下傳播
有時(shí)候,你希望停止事件被傳播到其它監(jiān)聽器,你可以通過從監(jiān)聽器的handle
方法中返回false
來實(shí)現(xiàn)。
需要將事件監(jiān)聽器放到隊(duì)列中?沒有比這更簡單的了,只需要讓監(jiān)聽器類實(shí)現(xiàn)ShouldQueue
接口即可,通過Artisan命令event:generate
生成的監(jiān)聽器類已經(jīng)將接口導(dǎo)入當(dāng)前命名空間,所有你可以立即拿來使用:
<?php
namespace App\Listeners;
use App\Events\PodcastWasPurchased;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class EmailPurchaseConfirmation implements ShouldQueue{
//
}
就是這么簡單,當(dāng)監(jiān)聽器被事件調(diào)用,將會使用Lumen的隊(duì)列系統(tǒng)通過隊(duì)列分發(fā)器自動隊(duì)列化。如果通過隊(duì)列執(zhí)行監(jiān)聽器的時(shí)候沒有拋出任何異常,隊(duì)列任務(wù)在執(zhí)行完成后被自動刪除。
手動訪問隊(duì)列
如果你需要手動訪問底層隊(duì)列任務(wù)的delete
和release
方法,在生成的監(jiān)聽器中默認(rèn)導(dǎo)入的Illuminate\Queue\InteractsWithQueue
trait提供了訪問這兩個(gè)方法的權(quán)限:
<?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);
}
}
}
要觸發(fā)一個(gè)事件,可以使用Event門面,傳遞一個(gè)事件實(shí)例到fire
方法,fire
方法會分發(fā)事件到所有監(jiān)聽器:
<?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));
}
}
此外,你還可以使用全局的幫助函數(shù)event
來觸發(fā)事件:
event(new PodcastWasPurchased($podcast));
在很多現(xiàn)代web應(yīng)用中,web套接字被用于實(shí)現(xiàn)實(shí)時(shí)更新的用戶接口。當(dāng)一些數(shù)據(jù)在服務(wù)器上被更新,通常一條消息通過websocket連接被發(fā)送給客戶端處理。
為幫助你構(gòu)建這樣的應(yīng)用,Lumen讓通過websocket連接廣播事件變得簡單。廣播Lumen事件允許你在服務(wù)端和客戶端JavaScript框架之間共享同一事件名。
Lumen支持多種廣播驅(qū)動:Pusher、Redis以及一個(gè)服務(wù)于本地開發(fā)和調(diào)試的日志驅(qū)動。每一個(gè)驅(qū)動都有一個(gè)配置示例。BROADCAST_DRIVER配置選項(xiàng)可用于設(shè)置默認(rèn)驅(qū)動。
廣播預(yù)備知識
事件廣播需要以下兩個(gè)依賴:
隊(duì)列預(yù)備知識
在開始介紹廣播事件之前,還需要配置并運(yùn)行一個(gè)隊(duì)列監(jiān)聽器。所有事件廣播都通過隊(duì)列任務(wù)來完成以便應(yīng)用的響應(yīng)時(shí)間不受影響。
要告訴Lumen給定事件應(yīng)該被廣播,需要在事件類上實(shí)現(xiàn)Illuminate\Contracts\Broadcasting\ShouldBroadcast
接口。ShouldBroadcast
接口要求你實(shí)現(xiàn)一個(gè)方法:broadcastOn
。該方法應(yīng)該返回事件廣播”頻道“名稱數(shù)組:
<?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;
/**
* 創(chuàng)建新的事件實(shí)例
*
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* 獲取事件廣播頻道
*
* @return array
*/
public function broadcastOn()
{
return ['user.'.$this->user->id];
}
}
然后,你只需要和正常一樣觸發(fā)該事件,事件被觸發(fā)后,一個(gè)隊(duì)列任務(wù)將通過指定廣播驅(qū)動自動廣播該事件。
如果某個(gè)事件被廣播,其所有的public
屬性都會按照事件負(fù)載自動序列化和廣播,從而允許你從JavaScript中訪問所有public
數(shù)據(jù),因此,舉個(gè)例子,如果你的事件有一個(gè)單獨(dú)的包含Eloquent模型的$user
屬性,廣播負(fù)載定義如下:
{
"user": {
"id": 1,
"name": "Jonathan Banks"
...
}
}
然而,如果你希望對廣播負(fù)載有更加細(xì)粒度的控制,可以添加broadcastWith
方法到事件,該方法應(yīng)該返回你想要通過事件廣播的數(shù)組數(shù)據(jù):
/**
* 獲取廣播數(shù)據(jù)
*
* @return array
*/
public function broadcastWith(){
return ['user' => $this->user->id];
}
Pusher
你可以通過Pusher的JavaScript SDK方便地使用Pusher驅(qū)動消費(fèi)事件廣播。例如,讓我們從之前的例子中消費(fèi)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消費(fèi)者來接收消息并使用自己選擇的websocket技術(shù)將其進(jìn)行廣播。例如,你可以選擇使用使用Node編寫的流行的Socket.io庫。
使用Node庫socket.io
和ioredis
,你可以快速編寫事件廣播發(fā)布所有廣播事件:
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);
});
事件訂閱者是指那些在類本身中訂閱到多個(gè)事件的類,從而允許你在單個(gè)類中定義一些事件處理器。訂閱者應(yīng)該定義一個(gè)subscribe
方法,該方法中傳入一個(gè)事件分發(fā)器實(shí)例:
<?php
namespace App\Listeners;
class UserEventListener{
/**
* 處理用戶登錄事件
*/
public function onUserLogin($event) {}
/**
* 處理用戶退出事件
*/
public function onUserLogout($event) {}
/**
* 為訂閱者注冊監(jiān)聽器
*
* @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'
);
}
}
訂閱者被定義后,可以通過事件分發(fā)器進(jìn)行注冊,你可以使用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{
/**
* 事件監(jiān)聽器映射數(shù)組
*
* @var array
*/
protected $listen = [
//
];
/**
* 要注冊的訂閱者
*
* @var array
*/
protected $subscribe = [
'App\Listeners\UserEventListener',
];
}
更多建議: