事件
簡介
laravel 的事件提供了一種簡單的觀察者實現(xiàn)收叶。它允許你在應用中進行訂閱和監(jiān)聽事件童太。事件類通常都是存儲在 app/Events
目錄中拆檬,而他們的監(jiān)聽者都是存儲在 app/Listeners
目錄中惜傲。
注冊事件/監(jiān)聽者
EventServiceProvider
提供了一個注冊所有事件監(jiān)聽者的方便的場所约素。它的 listen
屬性包含了一個所有事件(keys)以及他們的監(jiān)聽者(values)所組成的數(shù)組届良。當然,你可以在這個數(shù)組中添加任何你需要的事件圣猎。比如士葫,讓我們添加 PodcastWasPurchased
事件:
/**
* The event listener mappiings for the application.
*
* @var array
*/
protected $listen = [
'App\Events\PodcastWasPurchased' => [
'App\Listeners\EmailPurchaseConfirmation',
],
],
生成事件/監(jiān)聽者類
當然,每次都手動的去創(chuàng)建一個事件文件和一個監(jiān)聽者文件是很麻煩的事情送悔,所以慢显,你可以在 EventServiceProvider
中添加事件和監(jiān)聽者然后使用 event:generate
命令來自動生成。這個命令會生成 EventServiceProvider
中的所有列出的事件和監(jiān)聽者放祟,當然鳍怨,已經(jīng)存在的事件和監(jiān)聽者不會重新生成:
php artisan event:generate
手動的注冊事件
通常,事件應該被注冊在 EventServiceProvider
的 $listen
數(shù)組中跪妥,事實上鞋喇,你可以使用 Event
假面的事件分發(fā)器或者一個 Illuminate\Contracts\Events\Dispatcher
的實現(xiàn)來手動注冊事件:
/**
* Register any other events for your application
*
* @param \Illuminate\Contracts\Events\Dispatcher $events
* @return void
*/
public function boot(DispatcherContract $events)
{
parent::boot($events);
$events->listen('event.name', function ($foo, $bar) {
//
});
}
事件監(jiān)聽通配符
你可以在注冊監(jiān)聽器的時候使用 *
來作為通配符。這允許你來在同一個監(jiān)聽器中監(jiān)聽多種事件眉撵。通配符監(jiān)聽器接收整個事件數(shù)據(jù)數(shù)組作為第一個參數(shù):
$events->listen('event.*', function (array $data) {
//
});
定義事件
一個事件類只是簡單的數(shù)據(jù)存儲器侦香,它應該持有事件相關(guān)的信息.比如,讓我們假設(shè)我們生成的 PodcastWasPurchased
事件應該接收一個 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;
/**
* Create a new event instance.
*
* @param Podcast $podcast
* @return void
*/
public function __construct(Podcast $podcast)
{
$this->podcast = $podcast;
}
}
就如你所看到的纽疟,這個事件類并沒有包含什么業(yè)務邏輯罐韩。它只是簡單的包含了一個被購買的 Podcast
對象。SerializesModels
trait 被用來序列化 Eloquent 模型污朽,如果事件對象是使用 PHP 的 serialize
方法被進行序列化散吵,那么 SerializesModels
就會優(yōu)雅的將其內(nèi)部的 Eloquent 對象序列化。
定義監(jiān)聽者
接著蟆肆,讓我們來看一下針對我們上面舉例的事件的監(jiān)聽者矾睦。事件監(jiān)聽者會在其 handle
方法中接收事件的實例。event:generate
命令會自動的在 handle
方法中引入其對應的事件的類型提示炎功。你可以在 handle
方法中來提供對事件的響應邏輯:
<?php
namespace App\Listeners;
use App\Events\PodcastWasPurchased;
class EmailPurchaseConfirmation
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param PodcastWasPurchased $event
* @return void
*/
public function handle(PodcastWasPurchased $event)
{
// Access the podcast using $event->podcast...
}
}
你的事件監(jiān)聽者也可以在構(gòu)造函數(shù)中進行類型提示來注入依賴枚冗。所有的事件監(jiān)聽器都是通過 laravel 的服務容器解析的。所以蛇损,它們的依賴可以被自動的注入:
use Illuminate\Contracts\Mail\Mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
停止傳遞事件
有時候赁温,你可能希望停止傳遞事件到后續(xù)的監(jiān)聽者中坛怪。你可以在監(jiān)聽者的 handle
方法中返回 false
,這樣后續(xù)的監(jiān)聽者將不再進行事件的響應股囊。
監(jiān)聽者隊列化
需要對事件監(jiān)聽者進行隊列化袜匿?沒有比這更簡單的了。直接添加 ShouldeQueue
接口到監(jiān)聽者類中就可以了毁涉。通過 event:generate
Artisan 命令生成的監(jiān)聽者已經(jīng)在當前的命名空間中添加了對這個接口的支持沉帮,所以你立即就可以使用:
<?php
namespace App\Listeners;
use App\Events\PodcastWasPurchased;
use Illuminate\Contracts\Queue\ShouldQueue;
class EmailPurchaseConfirmation implements ShouldQueue
{
//
}
就這么簡單!當事件觸發(fā)時贫堰,事件分發(fā)器會使用 laravel 的隊列系統(tǒng)對監(jiān)聽器進行自動的隊列化調(diào)用穆壕。如果監(jiān)聽器隊列化執(zhí)行的過程中沒有異常出現(xiàn),隊列任務會自動的在監(jiān)聽器進程完成后進行刪除其屏。
手動的訪問隊列
如果你需要手動的訪問底層隊列任務的 delete
和 release
方法喇勋。那么你就可以直接去使用它們。Illuminate\Queue\InteractsWithQueue
trait 對默認生成的監(jiān)聽器提供了訪問這些方法的權(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ā)事件
你可以使用 Event
假面來進行事件的觸發(fā)偎行,你需要傳遞一個事件實例到 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
{
/**
* Show the profile for the given user.
*
* @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
幫助函數(shù)來觸發(fā)事件:
event( new PodcastWasPurchased($podcast));
廣播事件
很多現(xiàn)代化的應用中蛤袒,會使用 web sockets 來實現(xiàn)實時交互的用戶接口熄云。當一些數(shù)據(jù)在服務端變更時,一條消息會通過 websocket 連接來傳遞到客戶端進行處理妙真。
為了幫助你構(gòu)建這種類型的應用缴允。laravel 使通過 websocket 連接進行廣播事件變的非常簡單。laravel 允許你廣播事件來共享事件的名稱到你的服務端和客戶端的 JavaScript 框架珍德。
配置
所有的事件廣播配置選項都被存儲在 config/broadcasting.php
配置文件中练般。laravel 支持多種開箱即用的廣播驅(qū)動:Pusher,Redis 和 log
驅(qū)動來提供本地開發(fā)和調(diào)試锈候。在這個配置文件中包含了每種驅(qū)動的配置示例薄料。
廣播先決條件
事件廣播需要以下依賴:
- Pusher:
pusher/pusher-php-server ~2.0
- Redis:
predis/predis ~1.0
隊列化先決條件
在進行事件廣播之前,你需要先配置好隊列監(jiān)聽器泵琳。所有的廣播都是通過隊列任務來異步執(zhí)行的摄职,這樣應用響應就不會受到影響。
對事件進行廣播
你需要實現(xiàn) Illuminate\Contracts\Broadcasting\ShouldBroadcast
接口在事件類中获列,以通知 laravel 所給定的事件需要被廣播琳钉。ShouldBroadcast
接口只要求你實現(xiàn)一個單一的方法:broadcastOn
.該方法應該返回一個事件應該被廣播的通道名稱:
<?php
namespace App\Events;
use App\User;
use App\Events\Event;
use Illuminate\Queue\SerializesModles;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class ServerCreated extends Event implements ShouldBroadcast
{
use SerializesModels;
public $user;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Get the channels the event should be broadcast on.
*
* @return array
*/
public function broadcastOn()
{
return ['user.' . $this->user->id];
}
}
然后,你只需要像平常那樣去觸發(fā)事件就可以了蛛倦。一旦事件被觸發(fā),隊列任務會自動的廣播事件到你指定的廣播驅(qū)動中啦桌。
廣播數(shù)據(jù)
當一個事件被廣播時溯壶,事件類的所有 public
屬性都會被序列化并作為廣播的載荷及皂,允許你的前端 JavaScript 應用來訪問所有的開放屬性。比如且改,如果事件類中只有一個開放的 $user
屬性验烧,該屬性包含了一個 Eloquent 模型,那么廣播的載荷將會像這樣:
{
"user": {
"id": 1,
"name": "Jonathan Banks"
...
}
}
事實上又跛,你可以通過在事件中添加 broadcastWith
方法來對廣播載荷擁有更多的控制碍拆。該方法應該返回一個數(shù)組:
/**
* Get the data to broadcast.
*
* @return array
*/
public function broadcastWith()
{
return ['user' => $this->user->id];
}
定制化事件廣播
定制事件名稱
默認的,廣播事件的名稱會使用事件類的全名慨蓝。比如感混,如果事件的類名是 App\Events\ServerCreated
,那么廣播事件的名稱就是 App\Events\ServerCreated
礼烈。你可以通過定義 broadcastAs
方法來自定義廣播事件的名稱:
/**
* Get the broadcast event name
*
* @return string
*/
public function broadcastAs()
{
return 'app.server-created';
}
定制化隊列
默認的弧满,所有的事件廣播都是通過 queue.php
配置文件中所指定的默認隊列配置來進行的。你可以通過在事件廣播類中添加一個 onQueue
方法來指定所使用的隊列此熬。該方法應該返回一個你所期望使用的隊列的名稱:
/**
* Set the name of the queue the event should be placed on.
*
* @return string
*/
public function onQueue()
{
return 'your-queue-name';
}
對接廣播事件
Pusher
你可以使用 Pusher 驅(qū)動的 JavaScript SDK來輕松的對接廣播事件庭呜。比如,讓我們消費前面例子中的 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 發(fā)布/訂閱消費者來接收消息募谎,你可以自由的選擇使用 websocket 技術(shù)來進行事件的廣播。比如阴汇,你可以選擇使用基于 Node 受歡迎的 Socket.io 類庫数冬。
你可以使用 socket.io
和 ioredis
Node 類庫來快速的編寫事件廣播員來發(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 (subscrbed, channel, message) {
message = JSON.parse(message);
io.emit(channel + ':' + message.event, message.data);
});
事件訂閱
事件訂閱允許一個類在其內(nèi)部訂閱多個事件。這允許你在同一個文件中對多個事件進行處理鲫寄。訂閱者應該定義 subscribe
方法吉执。該方法會被傳遞一個事件分發(fā)器實例:
<?php
namespace App\Listeners;
class UserEventListener
{
/**
* Handle user login events.
*/
public function onUserLogin($event) {}
/**
* Handle user logout events.
*/
public function onUserLogout($event) {}
/**
* Register the listeners for the subscriber.
*
* @param Illuminate\Events\Dispatcher $events
*/
public function subscribe($events)
{
$events->listen(
'App\Events\UserLoggedIn',
'App\Listeners\UserEventListener@onUserLogin'
);
$events->listen(
'App\Events\UserLoggedOut',
'App\Listeners\UserEventListener@onUserLogout'
);
}
}
注冊一個事件訂閱者
當你的訂閱者被定義完成之后,它應該在事件分發(fā)器中進行注冊地来。你可以使用 EventServieProvider
的 $subscribe
屬性來注冊訂閱者戳玫。比如,讓我們來添加一個 UserEventListener
:
<?php
namespace App\Providers;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mapping for the application.
*
* @var array
*/
protected $listen = [
//
];
/**
* The subscriber classes to register.
*
* @var array
*/
protected $subscribe = [
'App\Listeners\UserEventListener',
];
}