事件系統


################

事件系統

事件系統介紹

Laravel 的事件提供了一個簡單的觀察者實現,能夠訂閱和監聽應用中發生的各種事件。事件類別通常存放在

app/Events 目錄下,而這些事件類別的監聽器則存放在 app/Listeners 目錄下。如果在你的應用程式中你沒有看到這些目錄,不用擔心,它們會在你使用 Artisan 控制台命令產生事件與監聽器的時候自動建立。

事件系統為應用各個方面的解耦提供了非常棒的方法,因為單一事件可以擁有多個互不依賴的監聽器。舉個例子,你可能會想要每次訂單出貨時向用戶推播一個 Slack 通知。你可以簡單地發起一個可以被監聽器接收並轉換為 Slack 通知的

OrderShipped 事件,而不是將訂單處理程式碼和 Slack 通知程式碼耦合在一起。

註冊事件與監聽器

Laravel 應用程式中的

EventServiceProvider 為註冊所有的事件監聽器提供了一個便利的場所。其中, listen 屬性包含了所有事件 (鍵) 以及事件對應的監聽器 (值) 的陣列。當然,你可以根據應用程式的需要,新增多個事件到 listen 屬性包含的陣列中。舉個例子,我們來新增一個 OrderShipped 事件:

/**
 * 应用程序的事件监听器映射
 *
 * @var array
 */
 protected $listen = [ 
    'App\Events\OrderShipped' => [    
        'App\Listeners\SendShipmentNotification',  
        ],
    ];

##

產生事件 & 監聽器

當然,手動建立事件和監聽器的檔案是件麻煩事。而在這裡,你只需要將監聽器和事件加入到 EventServiceProvider 中,而後使用 event:generate 指令。這個指令會產生在 EventServiceProvider 中列出的所有事件和監聽器。當然,已經存在的事件和監聽器將保持不變:

php artisan event:generate

#手動註冊事件

通常,事件是在EventServiceProvider$listen 陣列中註冊;然而,你也可以在EventServiceProviderboot 方法中手動註冊基於閉套件的這些事件:

/**
 * 注册应用中的其它事件
 *
 * @return void
 */
 public function boot(){ 
    parent::boot();    
    Event::listen('event.name', function ($foo, $bar) {  
          //   
       });
  }

通配符事件監聽器

你可以在註冊監聽器時使用* 作為通配符參數,這樣可以在同一個監聽器上捕捉多個事件。通配符監聽器接收事件名稱作為其第一個參數,並將整個事件資料數組作為其第二個參數:

Event::listen('event.*', function ($eventName, array $data) { 
   //
});

定義事件

事件類別是一個保存與事件相關資訊的容器。例如,假設我們產生的 OrderShipped 事件接收一個 Eloquent ORM 物件:

<?php
    namespace App\Events;
    use App\Order;
    use Illuminate\Queue\SerializesModels;
    class OrderShipped{ 
       use SerializesModels;    
       public $order;    
     /**
     * 创建一个事件实例。
     *
     * @param  \App\Order  $order
     * @return void
     */   
    public function __construct(Order $order) 
       {    
           $this->order = $order;  
        }
    }

如你所見,這個事件類別中沒有包含其它邏輯。它只是一個購買的 Order 的實例的容器。如果使用 PHP 的 serialize 函數序列化事件對象,事件使用的 SerializesModels trait 將會優雅地序列化任何 Eloquent 模型。

#定義監聽器

接下來,讓我們來看看例子中事件的監聽器。事件監聽器在 handle 方法中接收實例。 event:generate 指令會自動載入正確的事件類,並且在 handle 方法中加入事件的類型提示。在handle 方法中,你可以執行任何必要的回應事件的操作:

<?php
    namespace App\Listeners;
    use App\Events\OrderShipped;
    class SendShipmentNotification{  
      /**
     * 创建事件监听器。
     *
     * @return void
     */  
   public function __construct()
      {      
        // 
      }  
     /**
     * 处理事件。
     *
     * @param  \App\Events\OrderShipped  $event
     * @return void
     */ 
    public function handle(OrderShipped $event) 
       {    
           // 使用 $event->order 来访问 order ...  
        }
     }

{tip} 你的事件監聽器也可以在建構子中加入任何依賴關係的類型提示。所有的事件監聽器都是透過 Laravel 的 服務容器 來解析的,因此所有的依賴都會被自動注入。

停止事件傳播

有時,你可以透過在監聽器的handle 方法中傳回false來阻止事件被其它的監聽器取得。

事件監聽器佇列

如果你的監聽器中要執行諸如發送電子郵件或進行 HTTP 請求等比較慢的任務,你可以選擇將其丟給佇列處理。在開始使用佇列監聽器之前,請確保在你的伺服器或本機開發環境中能夠 設定佇列 並啟動一個佇列監聽器。

要指定監聽器啟動佇列,你可以在監聽器類別中增加 ShouldQueue 介面。由 Artisan 指令 event:generate 產生的監聽器已經將此介面匯入到目前命名空間中,因此你可以直接使用:

<?php
    namespace App\Listeners;
    use App\Events\OrderShipped;
    use Illuminate\Contracts\Queue\ShouldQueue;
    class SendShipmentNotification implements ShouldQueue{
        //
     }

就是這個!當這個監聽器被事件呼叫時,事件調度器會自動使用 Laravel 的 佇列系統。如果在佇列中執行監聽器時沒有拋出異常,任務會在執行完成後自動從佇列中刪除。

自訂佇列連線& 佇列名稱

如果你想要自訂事件監聽器所使用的佇列的連線和名稱,你可以在監聽器類別中定義$connection$queue$delay 屬性:

<?php
    namespace App\Listeners;
    use App\Events\OrderShipped;
    use Illuminate\Contracts\Queue\ShouldQueue;
    class SendShipmentNotification implements ShouldQueue{
       /**
     * The name of the connection the job should be sent to.
     *
     * @var string|null
     */  
   public $connection = 'sqs'; 
     /**
     * The name of the queue the job should be sent to.
     *
     * @var string|null
     */   
   public $queue = 'listeners';   
     /**
     * The time (seconds) before the job should be processed.
     *
     * @var int
     */ 
    public $delay = 60;
  }

#手動存取佇列如果你需要手動存取監聽器下面佇列任務的

delete

release 方法,你可以透過使用

Illuminate\ Queue\InteractsWithQueue
trait 來實作。這個trait 會預設載入到產生的監聽器中,並提供對這些方法的存取:
<?php
    namespace App\Listeners;
    use App\Events\OrderShipped;
    use Illuminate\Queue\InteractsWithQueue;
    use Illuminate\Contracts\Queue\ShouldQueue;
    class SendShipmentNotification implements ShouldQueue{
        use InteractsWithQueue;  
      /**
     * 处理事件。
     *
     * @param  \App\Events\OrderShipped  $event
     * @return void
     */   
    public function handle(OrderShipped $event)
        {     
           if (true) { 
                  $this->release(30);  
                  }  
          }
     }

處理失敗任務

有時事件監聽器的佇列任務可能會失敗。如果監聽器的佇列任務超過了佇列中定義的最大嘗試次數,則會在監聽器上呼叫

failed

方法。
failed

方法接收事件實例和導致失敗的異常作為參數:

<?php
    namespace App\Listeners;use App\Events\OrderShipped;
    use Illuminate\Queue\InteractsWithQueue;
    use Illuminate\Contracts\Queue\ShouldQueue;
    class SendShipmentNotification implements ShouldQueue{ 
       use InteractsWithQueue;   
       /**
     * 处理事件。
     *
     * @param  \App\Events\OrderShipped  $event
     * @return void
     */   
    public function handle(OrderShipped $event) 
       {    
           // 
        }  
    /**
     * 处理失败任务。
     *
     * @param  \App\Events\OrderShipped  $event
     * @param  \Exception  $exception
     * @return void
     */  
    public function failed(OrderShipped $event, $exception)
        {   
             //  
         }
     }

#分發事件如果要分發事件,你可以將事件實例傳遞給輔助函數

event
。此輔助函數將會把事件分發到所有該事件對應的已經註冊了的監聽器上。
event### 輔助函數可以全域使用,你可以在應用程式中的任何位置進行呼叫:###
<?php
    namespace App\Http\Controllers;
    use App\Order;use App\Events\OrderShipped;
    use App\Http\Controllers\Controller;
    class OrderController extends Controller{   
       /**
     * 将传递过来的订单发货
     *
     * @param  int  $orderId
     * @return Response
     */   
   public function ship($orderId)
       {     
          $order = Order::findOrFail($orderId);        
          // 订单发货逻辑 ...        
          event(new OrderShipped($order));    
        }
       }
######{tip} 在測試時,只需要斷言特定事件被分發,而不需要真正觸發監聽器。 Laravel 的 內建測試輔助函數 可以輕鬆做到這一點。 ###########################事件訂閱者#################### #

編寫事件訂閱者

事件訂閱者是可以在自身內部訂閱多個事件的類,也就是能夠在單一類別中定義多個事件處理器。訂閱者應該定義一個 subscribe 方法,這個方法接收一個事件分發器實例。你可以呼叫給定事件分發器上的listen 方法來註冊事件監聽器:

<?php
    namespace App\Listeners;
    class UserEventSubscriber{  
      /**
     * 处理用户登录事件。
     */   
   public function onUserLogin($event) {}    
      /**
     * 处理用户注销事件。
     */   
   public function onUserLogout($event) {}   
    /**
     * 为订阅者注册监听器
     *
     * @param  \Illuminate\Events\Dispatcher  $events
     */   
    public function subscribe($events) 
       {      
         $events->listen(         
            'Illuminate\Auth\Events\Login',           
            'App\Listeners\UserEventSubscriber@onUserLogin'       
           );        
          $events->listen(         
             'Illuminate\Auth\Events\Logout',            
             'App\Listeners\UserEventSubscriber@onUserLogout'    
              );  
         }
      }

##註冊事件訂閱者

在寫完訂閱者之後,就可以透過事件分發器對訂閱者進行註冊。你可以在

EventServiceProvider 中的 $subscribe 屬性中註冊訂閱者。例如,讓我們將 UserEventSubscriber 加入到陣列列表中:

<?php
    namespace App\Providers;
    use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
    class EventServiceProvider extends ServiceProvider{   
     /**
     * 应用中事件监听器的映射。
     *
     * @var array
     */   
   protected $listen = [  
         //  
        ];  
     /**
     * 需要注册的订阅者类。
     *
     * @var array
     */ 
    protected $subscribe = [ 
           'App\Listeners\UserEventSubscriber',   
          ];
      }

本文章首發在
LearnKu.com 網站上。