yii2 - Event 實例及源碼分析

yii2 能夠方便的使用 Event 組件基類 來實現 註冊事件和監聽觸發 機制,每一個事件都有自身的事件隊列php

首先要知道,yii2的事件一共有三類:對象級,類級,全局 \Yii::$app 級數組

對象和 全局是經過 yii\base\Component 管理的yii2

類級 是經過 yii\base\Event 管理的app

yii\base\Object 

    |--yii\base\Behavior

    |--yii\base\Event

    |--yii\base\Component

        |--yii\base\Application

        |--yii\base\Controller

        ..............

並且要注意的一點是:對象的事件隊列觸發完畢後會隱式的檢測對象所屬的類是否也存在此事件的隊列,若存在,則會使用 Event::trigger($this, $event_name, $event = null) 進行觸發,$this 傳遞進去會被提取類名,進而使得管理類級別的 Event 也能夠執行事件,看下源碼就知道了yii

yii\base\Event 定義和實現了event的一些基礎屬性和類級別的方法。雖然在對象級的事件中處理時並無直接調用 Event 中的類級別的事件方法,但傳遞給 handler 方法的 event 都是 Event 的實例。this

在 yii\base\Component 中重寫了事件的 on/trigger 將其轉變爲對象級別的方法。要注意,yii2 會始終檢測此對象的類是否有註冊相同的事件,若註冊則一樣會聯動觸發,此時雖然是觸發類級別的事件,但觸發事件的上下文依然是對象級別的,默認的 event->sender 依然是當前對象spa

//觸發的事件名稱
    public $name;
    //觸發的傳遞者 若是是對象級的事件則sender默認爲此對象$this 若是是類級別的則默認爲 null
    public $sender;
    //是否終端事件隊列的執行
    //由於事件註冊的 handler 方法能夠接收 $event 對象 因此若是在內部將 $event->handled設爲 true 後
    //事件隊列將會終端再也不向下執行
    public $handled = false;
    //用於傳遞的數據
    public $data;

以上爲 Event 基類的屬性,咱們能夠繼承它來擴展咱們本身想要的 $event 回調參數的字段code


yii\base\Component 的 on 方法

咱們經常使用的組件:控制器、模型等組件的事件都是繼承於 Component 的對象

經過 on 方法向自身的 _events 隊列數組註冊事件,默認掛載到隊列尾部,根據時序性執行,但你也能夠傳參 append 爲 false 將此事件從隊列頭壓入,會優先於當前已註冊的事件執行繼承

$name   事件名稱

$hander 負責處理方法 此方法會接受 Event 的對象 咱們能夠將數據填充給此對象作到傳參的目的

$handled 標註事件隊列是否處理完成,true的話講不會再執行隊列中的剩餘事件

$data 這裏的data 最終會賦值給 $event->data 屬性,能夠傳遞一些參數,但注意他對標量並不會延遲綁定,好比你註冊時傳遞 $this->name,則是以 name 屬性的當前值存儲的,後續 name 改變,觸發時這個改變也沒法傳遞給 data,這裏傳遞 $this 對象自己比較靠譜,能夠適當的提升靈活性

/** 
     * @param string $name the event name
     * @param callable $handler the event handler
     * @param mixed $data the data to be passed to the event handler when the event is triggered.
     * When the event handler is invoked, this data can be accessed via [[Event::data]].
     * @param boolean $append whether to append new event handler to the end of the existing
     * handler list. If false, the new handler will be inserted at the beginning of the existing
     * handler list.
     * @see off()
     */
    public function on($name, $handler, $data = null, $append = true)
    {
        $this->ensureBehaviors();
        if ($append || empty($this->_events[$name])) {
            $this->_events[$name][] = [$handler, $data];
        } else {
            array_unshift($this->_events[$name], [$handler, $data]);
        }
    }

yii\base\Component 的 trigger 方法

trigger 能夠不傳遞 Event 參數,內部默認會給你 new 一個 yii\base\Event 對象,並默認將 sender 屬性默認爲當前調用觸發的對象$this,固然咱們也能夠傳遞一個 Event 的實例來自定義設定。

public function trigger($name, Event $event = null)
    {
        $this->ensureBehaviors();
        if (!empty($this->_events[$name])) {
            if ($event === null) {
                $event = new Event;
            }
            if ($event->sender === null) {
                $event->sender = $this;
            }
            $event->handled = false;
            $event->name = $name;
            foreach ($this->_events[$name] as $handler) {
                $event->data = $handler[1];
                call_user_func($handler[0], $event);
                // stop further handling if the event is handled
                if ($event->handled) {
                    return;
                }
            }
        }
        // invoke class-level attached handlers
        Event::trigger($this, $name, $event);
    }

注意:最後的 Event::trigger($this, $name, $event),即咱們以前說的此對象的類若是也有註冊相同事件,則也會被觸發,同時要注意這裏是對象級別隱式調用事件,傳遞的 $class 爲 $this,若是傳遞的 $event->sender屬性爲 null 的話在下面的執行中會被默認爲 $this,嗯,也就像咱們以前說的 對象觸發默認 sender 爲本對象,類觸發默認 sender 爲 null


yii\base\Event::on 方法

與 yii\base\Component::on 沒有太大區別,都是管理一個事件的隊列,這裏只不過須要給出類名而已

yii\base\Event::trigger 方法

public static function trigger($class, $name, $event = null)
    {
        if (empty(self::$_events[$name])) {
            return;
        }
        if ($event === null) {
            $event = new static;
        }
        $event->handled = false;
        $event->name = $name;
        
        //這裏的判斷主要是針對對象隱式調用自身類的事件
        if (is_object($class)) {
            if ($event->sender === null) {
                $event->sender = $class;
            }
            $class = get_class($class);
        } else {
            $class = ltrim($class, '\\');
        }

        $classes = array_merge(
            [$class],
            class_parents($class, true),
            class_implements($class, true)
        );

        foreach ($classes as $class) {
            if (!empty(self::$_events[$name][$class])) {
                foreach (self::$_events[$name][$class] as $handler) {
                    $event->data = $handler[1];
                    call_user_func($handler[0], $event);
                    if ($event->handled) {
                        return;
                    }
                }
            }
        }
    }

Event::trigger 的 $class 若是是對象時則會被提取類名


使用方法

對象級別

yii\base\Component::on($event_name, $handler_method, $event_data = null,$is_append = true)
//註冊 handler是匿名方法
$this->on("my_first_event", function($event) {
    // 事件名稱:my_first_event
    echo $event->name;
    // 觸發者:當前對象,我這裏拿到它的類名
    echo $event->sender->className();
    // 傳遞的數據
    echo $event->data;
    // 若是傳遞了 stop 的信號
    if ($event->data == "stop") {
        // 本事件的隊列將會終止 後續註冊的事件也不會被觸發執行了
        $event->handled = true;
    }
}, "data you can get with event->data");

// handler 是對象方法
$this->on("my_first_event", [$this, 'eventHandler'], "you can get with event->data");

// handler 是類方法
$this->on("my_first_event", ["app\handlers", 'eventHandler'], "you can get with event->data");

// append 設爲false,直接掛載到事件隊列頭優先執行,默認 true 是掛載到事件隊列尾步的
$this->on("my_first_event", function($event) {
    echo "though i am the last one, but i can execute firstly!"
}, "this is event data you can get with event->data", false);


//觸發 默認第二個參數Event的實例爲空
$this->trigger("my_first_event");

//本身定義一個 event
//要注意這裏傳遞的 event 的 name data 參數是沒法改變的,會在trigger 方法裏自動配置
//handled 參數也不可能手動設定,handled 是在你的 handler 方法中來決定的 
$this->trigger("my_first_event", new Event(["sender" => "default is $this now i change it"]));

//傳遞的 event 必須是 Event 的實例,因此咱們也能夠經過 Event 的子類來擴展數據
$this->trigger("my_first_event", new MyEvent([
    "desc" => "sub class of Event and expand property",
    "title" => "i can pass some other data",
    "id" => "33"
]));

/** MyEvent
|class MyEvent extends \yii\base\Event
|{
|    public $desc;
|    public $title;
|    public $id;
|}
*/

類級別

yii\base\Event::on($class, $event_name, $handler_method, $event_data = null,$is_append = true)

其實和對象也沒太大區別,只是將事件定義和觸發提高到類的層次,須要手動指定是哪一個類的事件而已

Event::on(__CLASS__, "my_first_event", [__CLASS__, 'classEventHandler'], "event->data");
//要注意這裏若是不傳遞 event 的話,默認的 event->sender 爲 null 對象級別的默認的是當前對象 $this
Event::trigger(__CLASS__, "my_first_event");
//給他一個sender
Event::trigger(__CLASS__, "my_first_event", new Event(['sender' => "this is a class"]));

public static funtion classEventHandler($event)
{
    echo $event->name . $event-> sender . $event->data;
    // if set handled is true the event queue will end and return
    $event->handled= true;
}

全局應用級別

\Yii::$app 是全局應用的句柄

\Yii::$app->on("can_be_trigger_anywhere", function($event) {
    echo "全局應用級別事件,能夠在應用的任何地方註冊或觸發"
})
\Yii::$app->trigger("can_be_trigger_anywhere");

能夠註冊全局事件,能夠在全局項目進行註冊和觸發


小結

 一、on 方法註冊事件,能夠附加數據,經過 handler 方法的 $event->data 接受,同時能夠設定append來絕對事件是否被優先執行

二、trigger 方法傳遞的 event 默認爲空時,若是爲類級別的觸發則 $event->sender 爲 null,若是爲對象級別的觸發則 $event->sender 爲當前對象 $this

三、每一個事件都有一個本身的事件隊列,並且,咱們能夠經過 設定 $event->handled 的狀態來絕對本事件的處理隊列是否繼續下去

相關文章
相關標籤/搜索