程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> 關於PHP編程 >> zendframework 事件管理(二),zendframework事件

zendframework 事件管理(二),zendframework事件

編輯:關於PHP編程

zendframework 事件管理(二),zendframework事件


首先需要明確的幾個問題:

Q1、什麼是事件?

A:事件就是一個有名字的行為。當這個行為發生的時候,稱這個事件被觸發。

 

Q2、監聽器又是什麼?

A:監聽器決定了事件的邏輯表達,由事件觸發。監聽器和事件往往是成對的,當然也可以是一個事件對應多個監聽器。監聽器是對事件的反應。當事件被觸發時,由監聽器做出反應。這樣一來,多個事件的觸發可以導致一個監聽器做出反應。一個事件也可以有多個監聽器做出反應。(一句話:監聽器和事件之間的關系既可以是一對多,也可以是多對一)

 

Q3、事件管理器又是干嘛的?

A:事件管理器(EventManager),從名字上就可以看出來是管理事件用的。但他怎麼管理呢?事件管理器往往會為多個事件聚合多個監聽器(這裡的事件和監聽器都是不定數【就是可以是一個也可以是多個】)。事件管理器還負責觸發事件。

  一般來說我們用對象來表示事件。一個事件對象描述了事件的基本元素,包括何時以及如何觸發這個事件。

  關於事件的基本元素:事件名稱、target(觸發事件的對象,一般是事件對象本身)、事件參數。之前我們講過事件相當與一個行為,在程序裡面我們經常使用方法或函數來表示行為。因此事件的參數往往也是函數的參數。

  另外關於Shared managers: 之前講過一個事件可以針對多個監聽器。這就是通過Shared managers實現的。EventManager的實現包含(組合)了SharedEventManagerInterface【在構造函數或者setSharedManager裡面使用了代碼注入的方式,詳情可以查看源碼】),而SharedEventManagerInterface描述了一個聚合監聽器的對象,這些監聽器只連接到擁有指定識別符的事件。SharedEventManager並不會觸發事件,他只提供監聽器並連接到事件。EventManger通過查詢SharedEventMangaer來獲取具有特定標識符的監聽器。

  EventManager裡面幾個重要的行為:

1、創建事件:創建事件實際上只是創建EventManagerInterface的一個實例

2、觸發事件:一般在事件行為裡面使用trigger觸發,這樣我們執行該行為的時候便可以直接觸發該事件。函數原型:trigger($eventName,$target=null,$argv=[]);$eventName一般為時間行為名(常用__FUNCTION__代替),$target則為事件對象本身可用$this代替,$argv為傳入事件的參數(一般為事件行為的參數)。

  當然事件觸發方式不僅僅只有trigger一種,還有triggerUntil,triggerEvent,triggerEventUntil。從名字上我們就可以看出分兩類:trigger和triggerEvent;trigger類只單純的觸發事件,不需要實現創建事件實例只需要一個事件名字就可以了,而trigger不僅觸發事件還順帶著觸發監聽器,需要事件實例。而帶有Until後綴的方法都需要一個回調函數,每一個監聽器的結果都會傳到該回調函數中,如果回調函數返回了一個true的bool值,EventManager必須使監聽器短路。(關於短路見下文的短回路)

更多內容請查看官方API,或者EventMangerInterface的具體注釋。

3、創建監聽器並連接到事件

  監聽器可以通過EventManager創建,也可以通過SharedEventManager創建。兩者都是使用attach方法,但參數有點兒不一樣。

  我們先看EventManager的方式:

方法原型:attach($eventName, callable $listener, $priority = 1)

  很簡單,我們只需要事件名,還有一個可調用函數,最後是優先級默認為1(zend裡面的自帶事件的優先級多為負數,所以如果你想讓自定義的監聽器優先級比較高,直接賦值一個正數就行了。)

  可調用函數也就是我們的監聽器。事件名有個特殊情況:“*”。這類似於正則匹配,將所有的事件都連接到本監聽器中。

  我們現在看看SharedEventManager方式:

方法原型:attach($identifier, $eventName, callable $listener, $priority = 1);

  與之前唯一不同的地方多了個identifier參數。關於identifier的源碼注釋如下:

used to pull shared signals from SharedEventManagerInterface instance;

用來從SharedEventmanager實例中拉取分享信號。identifier是一個數組,按照我的理解:如果一個事件(注意SharedEventmanager無法創建事件的)定義了identifier,就意味著該事件是可共享的。讓後SharedEventManger實例使用attach創建監聽器的時候傳入identifier參數。EventManager就可以使用identifier參數查詢所有的監聽器。

  令人困惑的是既然有了事件名,那就可以通過事件名來查詢相關監聽器,那為何還要多此一舉的添加identifier屬性?我考慮到的是事件繼承問題:假設有一個事件類Foo包含一個事件行為act,SubFoo繼承了Foo類並重寫了裡面的事件行為act。兩個類都的事件行為都具有相同的事件名act。這時候如果通過事件名來查詢監聽器,顯然會有沖突。這時候我們定義identifier[__CLASS__, get_class($this)],並在監聽器中指定identifier為SubFoo,顯然會匹配到SubFoo類中的事件行為act。

 

  以上我們通過SharedEventManager可以監聽多個事件,另外我們還可以通過listener aggregates實現。通過Zend\EventManager\ListenerAggregateInterface,讓一個類監聽多個事件,連接一個或多個實例方法作為監聽器。同樣的該接口也定義了attach(EventManagerInterface $events)和detach(EventManagerInterface $events)。我們在attach的具體實現中使用EventManager的實例的方法attach監聽到多個事件。

use Zend\EventManager\EventInterface;
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
use Zend\Log\Logger;

class LogEvents implements ListenerAggregateInterface
{
    private $listeners = [];
    private $log;

    public function __construct(Logger $log)
    {
        $this->log = $log;
    }

    public function attach(EventManagerInterface $events)
    {
        $this->listeners[] = $events->attach('do', [$this, 'log']);
        $this->listeners[] = $events->attach('doSomethingElse', [$this, 'log']);
    }

    public function detach(EventCollection $events)
    {
        foreach ($this->listeners as $index => $listener) {
            $events->detach($listener);
            unset($this->listeners[$index]);
        }
    }

    public function log(EventInterface $e)
    {
        $event  = $e->getName();
        $params = $e->getParams();
        $this->log->info(sprintf('%s: %s', $event, json_encode($params)));
    }
}

使用Aggregate的好處:

1、允許你使用有狀態的監聽器

2、在單一的類中組合多個相近的監聽器,並一次性連接他們

 

內省監聽器返回的結果

  我們有了監聽器,但如何接收他返回的結果呢?EventManager默認實現會返回一個ResponseCollection的實例。這個類繼承於PHP的SplStack。基本結構是一個棧,所以允許你反序遍歷Responses。

  ResponseCollection提供了有用的幾個方法:

first():  獲取第一個結果

last():  獲取最後一個結果

contains($value):  查看是否棧裡面是否包含某一個值,如果包含則返回true,否則false。

 

短回路監聽器執行:

  什麼叫短回路呢?假設你要做一件事情,直到這件事有了結果,這是一個回路。如果你提前知道了這件事的結果(比如之前做過這件事),那你就沒比要把這件事完完全全的做完,這時候你只需要執行一個短回路。

  我們在添加EventManager的時候有一個緩存機制。在一個方法中觸發一個事件,如果我們找到一個緩存結果就直接返回。如果找不到緩存結果,我們就將觸發的事件緩存下來以備後用。實際上和計算機硬件裡面的高速緩存一個道理。

  EventManager組件提供兩種處理的方式:1、triggerUntil();2、triggerEventUntil。這兩個方法都接受一個回調函數作為第一個參數。如果回調函數返回true,那執行停止。

public function someExpensiveCall($criteria1, $criteria2)
{
    $params = compact('criteria1', 'criteria2');
    $results = $this->getEventManager()->triggerUntil(
        function($r){
            return ($r instanceof SomeResultClass);
        },
        __FUNCTION__,
        $this,
        $params
    );

    if($results->stopped()) {
        return $results->last()'
    }
}

從上面范例中,我們知道,如果執行停止了很有可能是因為棧裡面最後的結果滿足我們的要求。這樣一來,我們只要返回該結果,何必還要進行多余的計算呢?

  處理在事件中停止執行,我們還可以在監聽器中停止執行。理由是我們曾經接收過某一個事件,現在我們又接收到了相同事件,理所當然的使用之前的結果就好了。這種情況下,監聽器調用stopPropagation(true),然後EventManager會直接返回而不會繼續通知額外的監聽器。

$events->attach('do', function($e) {
    $e->stopPropagation();
    return new SomeResultClass();
});

當然,使用觸發器范例可能會導致歧義,畢竟你並不知道最終的結果是否滿足要求。

  Keeping it in order.

  偶爾你會關心監聽器的執行順序。我們通過監聽器的優先級來控制執行順序(上面說講的短回路也會影響執行順序)。每一個EventManager::attach()和SharedEventManager::attach()都會接受一個而外的參數:priority。默認情況下為1,我們可以省略該參數。如果你提供了該參數:高優先級執行的早,低優先級的可能會推遲執行。

  自定義事件對象:

  我們之前使用trigger()觸發事件,在這同時我們也創建了事件。但trigger()的參數有限,我們只能指定事件的對象,參數,名稱。實際上我們可以創建一個自定義事件,在Zendframework裡面有個很重要的事件:MvcEvent。很顯然MvcEvent便是一個自定義事件,該事件組合了application實例,路由器,路由匹配對象,請求和應答對象,視圖模型還有結果。我們查看MvcEvent的源碼會發現MvcEvent類實際上繼承了Event類。同理我們的自定義事件對象也可以繼承Event類或者繼承MvcEvent。

$event = new CustomEvent();
$event->setName('foo');
$event->setTarget($this);
$event->setSomeKey($value);

//injected with event name and target:
$events->triggerEvent($event);

//Use triggerEventUntil() for criteria-based short-circuiting:
$results = $events->triggerEventUntil($callback, $event);

上面的代碼可以看到我們使用自定義事件類創建了一個事件對象,調用相關攔截器為事件對象設置屬性。我們有了事件對象還是用trigger()觸發事件嗎?顯然不是,我們使用triggerEvent($event)方法,該方法接收一個事件對象。而triggerEventUntil有一個回調函數,該回調函數作為是否進行短回路的依據。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved