一对好基友 - yii2行为和事件那些事 源码分析篇

658 阅读2分钟

上一篇用一个小例子让大家看到了当行为遇到事件,注入能力是多么强,这节课我来抛开它的面纱,你会发现?

我靠,原来这么简单。: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 函数,看看它出现的各种场景,这将对你学习行为有很大的好处。