Laravel 事件系統

iffor發表於2021-05-26

Laravel 事件系統

Dispatcher 是一個觀察者模式的實現,它最大的優點就是在物件之間沒有依賴關係的情況下,把物件內部的狀態暴露給對該狀態感興趣的物件,
通常我們把這類物件稱為監聽器,監聽器的本質是一個可執行的函式;狀態稱為事件,通常事件都有一個唯一的名稱,和攜帶一個可選的狀態資料。

通過下面的例子可以體驗下事件系統的優點。


class Page
{
    protected $dispatcher;

    public function __construct(Dispatcher $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }


    public function render()
    {
        $this->dispatcher->dispatch('css.loaded');

        $this->dispatcher->dispatch('javascript.loaded');

        $this->dispatcher->dispatch('html.painting');

        $this->dispatcher->dispatch('html.painted');
    }
}

$dispatcher = new Dispatcher();
$dispatcher->listen('css.loaded', function () {
    echo "css loaded ok \n";
});

$dispatcher->listen('javascript.loaded', function () {
    echo "javascript loaded ok \n";
});

$dispatcher->listen('html.painting', function () {
    echo "ready for  painting\n";
});
$dispatcher->listen('html.painted', function () {
    echo "render ok \n";
});


$page = new Page($dispatcher);
$page->render();

在寫 render 方法不用考慮對外的依賴關係,只需要把相應的事件派發就好,這些事件派發的點,本質就是一個功能延申擴充套件的切入點。

這種用法在 laravel 很多地方都有使用,例如每個 http 請求被處理完成之後都有派發一個 new RequestHandled($request, $response)
這個事件表示請求已經被處理,如果你想在請求被處理完成之後,做一個額外的操作–比如記錄日誌,就可以監聽該事件。

基本使用

通常 Dispatcher 會作為一個全域性的單例物件被使用,在系統初始化的事件設定監聽器,然後在具體業務邏輯中派發相應的事件。

定義事件

事件通常有兩部分組成

  • 事件的唯一名稱,為了簡單,如果事件是一個類,可以使用類的全名稱作為事件的唯一標記;
  • 事件攜帶的資料,這部分是可選;

RequestHandled 就是一個事件,它的事件名稱為Illuminate\Foundation\Http\Events\RequestHandled
它包含了請求物件和相應物件兩個資料屬性。


class RequestHandled
{
    /**
     * The request instance.
     *
     * @var \Illuminate\Http\Request
     */
    public $request;

    /**
     * The response instance.
     *
     * @var \Illuminate\Http\Response
     */
    public $response;

    /**
     * Create a new event instance.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Illuminate\Http\Response  $response
     * @return void
     */
    public function __construct($request, $response)
    {
        $this->request = $request;
        $this->response = $response;
    }
}

事件監聽器設定

使用 public function listen($events, $listener = null) 設定監聽器,通常有以下4種用法

  • $events 為事件的唯一名稱,$listener 為一個函式
  • $events 為一個函式,但函式的第一個引數為事件的唯一名稱,$listener 為空
  • $events 為事件的唯一名稱 $listener 為一個監聽器類的名稱,但是該類必須有 handle 方法或者 __invoke
  • $events為事件的唯一名稱$listener` 為一個陣列,陣列的第一個元素為監聽器類名稱,第二個引數為處理事件的方法名稱

// 方式一 函式監聽器
$dispatcher->listen(RequestHandled::class,function (RequestHandled  $event){
    echo sprintf("請求%s已經被處理,相應的狀態碼為%d\n",$event->request->url(),$event->response->status());
});


// 方式二 省略事件名稱
$dispatcher->listen(function (RequestHandled  $event){
    echo sprintf("請求%s已經被處理,相應的狀態碼為%d\n",$event->request->url(),$event->response->status());
});


//方式三:使用類監聽器,預設是使用監聽器的`handle`方法處理,如果找不到則使用`__invoke`方法,否則處丟擲異常
class RequestHandledListener{
    public function handle($event){
        echo sprintf("請求%s已經被處理,相應的狀態碼為%d\n",$event->request->url(),$event->response->status());
    }
}
$dispatcher->listen(RequestHandled::class,RequestHandledListener::class);

// 方式四:指定監聽器處理的方法
class RequestHandledListenerMethod{
    public function hand2($event){
        echo sprintf("請求%s已經被處理,相應的狀態碼為%d\n",$event->request->url(),$event->response->status());
    }
}

$dispatcher->listen(RequestHandled::class,[RequestHandledListenerMethod::class,'hand2']);

事件訂閱器

事件訂閱器的是一個帶有 subscribe 方法的類,subscribe 方法內部實現一些列的監聽器的設定。

下面例子的本質是把使用者相關的事件監聽器的設定封裝到一個名為 UserEventSubscriber

class UserEventSubscriber
{
    /**
     * Handle user login events.
     */
    public function handleUserLogin($event) {}

    /**
     * Handle user logout events.
     */
    public function handleUserLogout($event) {}

    /**
     * Register the listeners for the subscriber.
     *
     * @param  \Illuminate\Events\Dispatcher  $events
     * @return void
     */
    public function subscribe($events)
    {
        $events->listen(
            'Illuminate\Auth\Events\Login',
            [UserEventSubscriber::class, 'handleUserLogin']
        );

        $events->listen(
            'Illuminate\Auth\Events\Logout',
            [UserEventSubscriber::class, 'handleUserLogout']
        );
    }
}


$dispatcher->subscribe(UserEventSubscriber::class); // 設定訂閱器
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章