[TOC]
### **1、簡介**
[Laravel](http://laravelacademy.org/tags/laravel "View all posts in Laravel")?[事件](http://laravelacademy.org/tags/%e4%ba%8b%e4%bb%b6 "View all posts in 事件")提供了簡單的觀察者模式實現,允許你[訂閱](http://laravelacademy.org/tags/%e8%ae%a2%e9%98%85 "View all posts in 訂閱")和監聽應用中的事件。事件類通常存放在?`app/[Event](http://laravelacademy.org/tags/event "View all posts in Event")s`?目錄,[監聽器](http://laravelacademy.org/tags/%e7%9b%91%e5%90%ac%e5%99%a8 "View all posts in 監聽器")存放在`app/Listeners`。
### **2、注冊事件/監聽器**
Laravel 自帶的?`EventServiceProvider`?為事件注冊提供了方便之所。其中的?`listen`?屬性包含了事件(鍵)和對應監聽器(值)數組。如果應用需要,你可以添加多個事件到該數組。例如,讓我們添加?`PodcastWasPurchased`?事件:
~~~
/**
* 事件監聽器映射
*
* @var array
*/
protected $listen = [
'App\Events\PodcastWasPurchased' => [
'App\Listeners\EmailPurchaseConfirmation',
],
];
~~~
#### **2.1 生成事件/監聽器類**
當然,手動為每個事件和監聽器創建文件是很笨重的,取而代之地,我們可見簡單添加監聽器和事件到`EventServiceProvider`?然后使用`event:generate`命令。該命令將會生成羅列在?`EventServiceProvider`?中的所有事件和監聽器。當然,已存在的事件和監聽器不會被創建:
~~~
php artisan event:generate
~~~
#### **2.2 手動注冊事件**
一般我們需要將事件注冊到?`EventServiceProvider`?的?`$listen`?數組,此外,我們還可以使用?`Event`?門面或者`Illuminate\Contracts\Events\Dispatcher`?契約的具體實現類作為事件分發器手動注冊事件:
~~~
/**
* 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) {
//
});
}
~~~
##### **使用通配符作為事件監聽器**
你還可以使用通配符*來注冊監聽器,從而允許你通過同一個監聽器捕獲多個事件。通配符監聽器接收整個事件數據數組作為參數:
~~~
$events->listen('event.*', function (array $data) {
//
});
~~~
### **3、定義事件**
事件類是一個處理與事件相關的簡單數據容器,例如,假設我們生成的?`PodcastWasPurchased`?事件接收一個[Eloquent ORM](http://laravelacademy.org/post/2995.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`?方法中接收事件實例,`event:generate`?命令將會自動在?`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/2910.html)解析,所以依賴會自動注入:
~~~
use Illuminate\Contracts\Mail\Mailer;
public function __construct(Mailer $mailer){
$this->mailer = $mailer;
}
~~~
##### **停止事件繼續往下傳播**
有時候,你希望停止事件被傳播到其它監聽器,你可以通過從監聽器的?`handle`?方法中返回?`false`?來實現。
#### **事件監聽器[隊列](http://laravelacademy.org/tags/%e9%98%9f%e5%88%97 "View all posts in 隊列")**
需要將事件監聽器放到隊列?沒有比這更簡單的了,只需要讓監聽器類實現?`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{
//
}
~~~
就是這么簡單,當監聽器被事件調用,將會使用 Laravel 的隊列系統通過隊列分發器自動隊列化。如果通過隊列執行監聽器的時候沒有拋出任何異常,隊列任務在執行完成后被自動刪除。
##### **手動訪問隊列**
如果你需要手動訪問底層隊列任務的?`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/2920.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、[廣播](http://laravelacademy.org/tags/%e5%b9%bf%e6%92%ad "View all posts in 廣播")事件**
在很多現代 Web 應用中,Web 套接字被用于實現實時更新的用戶接口。當一些數據在服務器上被更新,通常一條消息通過 websocket 連接被發送給客戶端處理。
為幫助你構建這樣的應用,Laravel 讓通過 websocket 連接廣播事件變得簡單。廣播 Laravel 事件允許你在服務端和客戶端 JavaScript 框架之間共享同一事件名。
#### **6.1 配置**
所有的事件廣播配置選項都存放在?`config/broadcasting.php`?配置文件中。Laravel 支持多種廣播驅動:[Pusher](https://pusher.com/)、[Redis](http://laravelacademy.org/tags/redis "View all posts in Redis")以及一個服務于本地開發和調試的日志驅動。每一個驅動都有一個配置示例。
##### **廣播預備知識**
事件廣播需要以下兩個依賴:
* [Pusher](http://laravelacademy.org/tags/pusher "View all posts in Pusher"):?`pusher/pusher-php-server ~2.0`
* Redis:?`predis/predis ~1.0`
##### **隊列預備知識**
在開始介紹廣播事件之前,還需要配置并運行一個隊列監聽器。所有事件廣播都通過隊列任務來完成以便應用的響應時間不受影響。
#### **6.2 將事件標記為廣播**
要告訴 Laravel 給定事件應該被廣播,需要在事件類上實現`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];
}
}
~~~
然后,你只需要和正常一樣觸發該事件,事件被觸發后,一個隊列任務將通過指定廣播驅動自動廣播該事件。
#### **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 自定義事件廣播**
##### **自定義事件名**
默認情況下,廣播事件名就是事件類名,因此,如果事件的類名是?`App\Events\ServerCreated`,對應的廣播事件名就是`App\Events\ServerCreated`,你可以通過事件類上的?`broadcastAs`?方法自定義廣播事件名:
~~~
/**
* 獲取廣播事件名稱
*
* @return string
*/
public function broadcastAs()
{
return 'app.server-created';
}
~~~
##### **自定義隊列**
默認情況下,每個被廣播的事件都位于配置文件?`queue.php`?中定義的默認隊列連接中的默認隊列中,你可以通過事件類的?`onQueue`?方法自定義廣播事件的隊列名稱。該方法會返回你期望使用的隊列名:
~~~
/**
* 設置事件所在隊列的名稱
*
* @return string
*/
public function onQueue()
{
return 'your-queue-name';
}
~~~
#### **6.5 消費事件廣播**
##### **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);
io.emit(channel + ':' + message.event, message.data);
});
~~~
### **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'
);
}
}
~~~
##### **注冊一個事件訂閱者**
訂閱者被定義后,可以通過事件分發器進行注冊,你可以使用?`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',
];
}
~~~
- 序言
- 發行版本說明
- 升級指南
- 貢獻代碼
- 開始
- 安裝
- 配置
- Laravel Homestead
- 基礎
- HTTP 路由
- HTTP 中間件
- HTTP 控制器
- HTTP 請求
- HTTP 響應
- 視圖
- Blade 模板引擎
- 架構
- 一次請求的生命周期
- 應用目錄結構
- 服務提供者
- 服務容器
- 門面(Facades)
- 數據庫
- 起步
- 查詢構建器
- 遷移
- 填充數據
- Eloquent ORM
- 起步
- 關聯關系
- 集合
- 訪問器&修改器
- 序列化
- 服務
- 用戶認證
- 用戶授權
- Artisan Console
- 訂閱支付實現:Laravel Cashier
- 緩存
- 集合
- 集成前端資源:Laravel Elixir
- 加密
- 錯誤&日志
- 事件
- 文件系統/云存儲
- 哈希
- 輔助函數
- 本地化
- 郵件
- 包開發
- 分頁
- Redis
- 隊列
- Session
- Envoy Task Runner
- 任務調度
- 測試
- 驗證
- 新手入門指南
- 簡單任務管理系統
- 帶用戶功能的任務管理系統