hyperf/docs/zh-hk/event.md

6.5 KiB

事件機制

前言

事件模式必須基於 PSR-14 去實現。
Hyperf 的事件管理器默認由 hyperf/event 實現,該組件亦可用於其它框架或應用,只需通過 Composer 將該組件引入即可。

composer require hyperf/event

概念

事件模式是一種經過了充分測試的可靠機制,是一種非常適用於解耦的機制,分別存在以下 3 種角色:

  • 事件(Event) 是傳遞於應用代碼與 監聽器(Listener) 之間的通訊對象
  • 監聽器(Listener) 是用於監聽 事件(Event) 的發生的監聽對象
  • 事件調度器(EventDispatcher) 是用於觸發 事件(Event) 和管理 監聽器(Listener)事件(Event) 之間的關係的管理者對象

用通俗易懂的例子來説明就是,假設我們存在一個 UserService::register() 方法用於註冊一個賬號,在賬號註冊成功後我們可以通過事件調度器觸發 UserRegistered 事件,由監聽器監聽該事件的發生,在觸發時進行某些操作,比如發送用户註冊成功短信,在業務發展的同時我們可能會希望在用户註冊成功之後做更多的事情,比如發送用户註冊成功的郵件等待,此時我們就可以通過再增加一個監聽器監聽 UserRegistered 事件即可,無需在 UserService::register() 方法內部增加與之無關的代碼。

使用事件管理器

定義一個事件

一個事件其實就是一個用於管理狀態數據的普通類,觸發時將應用數據傳遞到事件裏,然後監聽器對事件類進行操作,一個事件可被多個監聽器監聽。

<?php
namespace App\Event;

class UserRegistered
{
    // 建議這裏定義成 public 屬性,以便監聽器對該屬性的直接使用,或者你提供該屬性的 Getter
    public $user;
    
    public function __construct($user)
    {
        $this->user = $user;    
    }
}

定義一個監聽器

監聽器都需要實現一下 Hyperf\Event\Contract\ListenerInterface 接口的約束方法,示例如下。

<?php
namespace App\Listener;

use App\Event\UserRegistered;
use Hyperf\Event\Contract\ListenerInterface;

class UserRegisteredListener implements ListenerInterface
{
    public function listen(): array
    {
        // 返回一個該監聽器要監聽的事件數組,可以同時監聽多個事件
        return [
            UserRegistered::class,
        ];
    }

    /**
     * @param UserRegistered $event
     */
    public function process(object $event)
    {
        // 事件觸發後該監聽器要執行的代碼寫在這裏,比如該示例下的發送用户註冊成功短信等
        // 直接訪問 $event 的 user 屬性獲得事件觸發時傳遞的參數值
        // $event->user;
        
    }
}

通過配置文件註冊監聽器

在定義完監聽器之後,我們需要讓其能被 事件調度器(Dispatcher) 發現,可以在 config/autoload/listeners.php 配置文件 (如不存在可自行創建) 內添加該監聽器即可,監聽器的觸發順序根據該配置文件的配置順序:

<?php
return [
    \App\Listener\UserRegisteredListener::class,
];

通過註解註冊監聽器

Hyperf 還提供了一種更加簡便的監聽器註冊方式,就是通過 @Listener 註解註冊,只要將該註解定義在監聽器類上,且監聽器類處於 Hyperf 註解掃描域 內即可自動完成註冊,代碼示例如下:

<?php
namespace App\Listener;

use App\Event\UserRegistered;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;

/**
 * @Listener 
 */
class UserRegisteredListener implements ListenerInterface
{
    public function listen(): array
    {
        // 返回一個該監聽器要監聽的事件數組,可以同時監聽多個事件
        return [
            UserRegistered::class,
        ];
    }

    /**
     * @param UserRegistered $event
     */
    public function process(object $event)
    {
        // 事件觸發後該監聽器要執行的代碼寫在這裏,比如該示例下的發送用户註冊成功短信等
        // 直接訪問 $event 的 user 屬性獲得事件觸發時傳遞的參數值
        // $event->user;
    }
}

在通過註解註冊監聽器時,我們可以通過設置 priority 屬性定義當前監聽器的順序,如 @Listener(priority=1) ,底層使用 SplPriorityQueue 結構儲存,priority 數字越大優先級越高。

使用 @Listener 註解時需 use Hyperf\Event\Annotation\Listener; 命名空間;

觸發事件

事件需要通過 事件調度器(EventDispatcher) 調度才能讓 監聽器(Listener) 監聽到,我們通過一段代碼來演示如何觸發事件:

<?php
namespace App\Service;

use Hyperf\Di\Annotation\Inject;
use Psr\EventDispatcher\EventDispatcherInterface;
use App\Event\UserRegistered; 

class UserService
{
    /**
     * @Inject 
     * @var EventDispatcherInterface
     */
    private $eventDispatcher;
    
    public function register()
    {
        // 我們假設存在 User 這個實體
        $user = new User();
        $result = $user->save();
        // 完成賬號註冊的邏輯
        // 這裏 dispatch(object $event) 會逐個運行監聽該事件的監聽器
        $this->eventDispatcher->dispatch(new UserRegistered($user));
        return $result;
    }
}

Hyperf 生命週期事件

注意事項

不要在 Listener 中注入 EventDispatcherInterface

因為 EventDispatcherInterface 依賴於 ListenerProviderInterface,而 ListenerProviderInterface 初始化的同時,會收集所有的 Listener

而如果 Listener 又依賴了 EventDispatcherInterface,就會導致循壞依賴,進而導致內存溢出。

最好只在 Listener 中注入 ContainerInterface

最好只在 Listener 中注入 ContainerInterface,而其他的組件在 process 中通過 container 獲取。框架啟動開始時,會實例化 EventDispatcherInterface,這個時候還不是協程環境,如果 Listener 中注入了可能會觸發協程切換的類,就會導致框架啟動失敗。

BootApplication 事件儘量避免 IO 操作

1.1.6 版本及更新的版本已優化此問題

1.1.6 版本之前,因為 BootApplication 是在 Command 初始化 和 Server 啟動前觸發,所以當前環境一定是非協程環境,一旦使用了協程 API,則會導致啟動失敗。