一對好基友 - yii2行為和事件那些事 原始碼分析篇

阿北哥ya發表於2017-08-24

上一篇用一個小例子讓大家看到了當行為遇到事件,注入能力是多麼強,這節課我來拋開它的面紗,你會發現?

我靠,原來這麼簡單。:triumph: :triumph: :triumph:

當然,這是源於你認真看了之前乾貨區的另一片文章 從behaviors()來研究元件繫結行為的原理

那我們就開始吧

思想準備階段

為了能循序漸進的學習,我們這篇還是以內建事件為例子,大家都知道,內建事件會被某些方法自動觸發,比如你在執行ar的save操作的時候,會觸發EVENT_BEFORE_INSERT和EVENT_AFTER_INSERT等事件,如果你之前在這些事件上繫結過實現,那麼這些實現邏輯就會被啟動。

那開始吧

嗯,那就開始吧,這一切要從ensureBehaviors函式講起,大家都知道,在繫結行為到元件的時候,它起到了保駕護航的作用,看程式碼

public function ensureBehaviors() {
    if ($this->_behaviors === null) {
        $this->_behaviors = [];
        foreach ($this->behaviors() as $name => $behavior) {
            $this->attachBehaviorInternal($name, $behavior);
        }
    }
}複製程式碼

而在 $this->attachBehaviorInternal($name, $behavior); 的方法裡有一個叫 $behavior->attach($this);的函式還記得麼?它將元件繫結到了行為物件自身並賦值了owner屬性。

回憶完了是吧,看看這個重要的函式吧。

// vendor/yiisoft/yii2/base/Behavior.php
public function attach($owner) {
    $this->owner = $owner;
    foreach ($this->events() as $event => $handler) {
        $owner->on($event, is_string($handler) ? [$this, $handler] : $handler);
    }
}複製程式碼

發現了吧,$owner->on($event, is_string($handler) ? [$this, $handler] : $handler);就是這一句,我們分析它。

這個函式在行為內

  • $owner 表示當前繫結了此行為的物件,也就是說這句話核心是元件類繫結了自己的事件。
  • $this 表示當前的行為物件
  • $handler 是行為events()方法返回陣列每一項的value值

好,各路神仙均已登場,開始順箇中關係。

首先$this有個方法events(),實現如下

public function events(){
    return [
        ActiveRecord::EVENT_BEFORE_INSERT => 'beforeInsert',
    ];
}複製程式碼

$handler此刻就是 beforeInsert 字串,對應的key是 ActiveRecord::EVENT_BEFORE_INSERT

attach函式首先將 $handler 對應的key(一個事件名)繫結到了 $owner 上,如果$handler是一個字串,則key事件的實現方法是行為類裡一個叫做 $handler()的函式,就是你看到的 [$this, $handler]。

如果不是字串,則這直接使用,它可以是符合事件繫結中的任何一種。(事件繫結方法傳送門

用例子說明

上面的內容有點枯燥,我們用昨天例子進行說明,當我們執行了$model->save()之後,行為會在User的username值後面新增一個"+"號,此刻你一定會有一個疑問。

之前我們能讓 ensureBehaviors起作用,是因為我們通過 __get & __call 實現了行為屬性和方法的注入,進而呼叫了ensureBehaviors函式,但是在昨天的例子中,我們並沒有顯性的呼叫HelloBehavior任何屬性和方法,那麼ensureBehaviors函式是如何被啟動的那?

無處不在的 ensureBehaviors

對,它真的無處不在,因為它同時也出現在了元件的事件觸發函式中,看程式碼

// vendor/yiisoft/yii2/base/Component.php
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);
}複製程式碼

就是在這個時候完成了行為內事件實現的注入行為。

這回看懂了吧,夠繞的。

你如果有興趣,可以整個專案搜尋下 ensureBehaviors 函式,看看它出現的各種場景,這將對你學習行為有很大的好處。


相關文章