从behaviors()来研究组件绑定行为的原理

阿北

不知不觉已经发布了7篇关于yii2行为的文章。传送门,今天再分享一篇到sf专栏。


为何使用 yiibaseComponent::behaviors() 就能绑定行为,发生了什么?

我们先来窥视一下类 Component 内部和绑定行为相关的函数。

  • yiibaseComponent::behaviors()

  • yiibaseComponent::ensureBehaviors()

  • yiibaseComponent::attachBehaviorInternal()

  • yiibaseBehavior::attach()

behaviors()

behaviors() 函数上一篇已经讲了,主要用来绑定行为的,里面接收各种要绑定的行为,它返回了一个数组,虽然我们现在知道配置这个函数能起到什么效果,但是还是要研究下,我们先在yii2的目录下搜索下都哪些函数用了此函数。

只有一句?是的,通过搜索我们发现只有一个函数调用了它 --- ensureBehaviors()。那就从它开始吧。

ensureBehaviors()

在研究它之前先看看代码

// 
/**
 * Makes sure that the behaviors declared in [[behaviors()]] are attached to this component.
 */
public function ensureBehaviors()
{
    if ($this->_behaviors === null) {
        $this->_behaviors = [];
        foreach ($this->behaviors() as $name => $behavior) {
            $this->attachBehaviorInternal($name, $behavior);
        }
    }
}

逻辑很简单,component组件类用一个属性 _behaviors 来存放它拥有的所有行为对象,如果判断为空,则调用$this->behaviors()函数获取一下,对每个行为执行 attachBehaviorInternal()函数。

attachBehaviorInternal()

看函数名 attachBehaviorInternal() 是绑定行为的意思,那就看一看。

private function attachBehaviorInternal($name, $behavior)
{
    if (!($behavior instanceof Behavior)) {
        $behavior = Yii::createObject($behavior);
    }
    if (is_int($name)) {
        $behavior->attach($this);
        $this->_behaviors[] = $behavior;
    } else {
        if (isset($this->_behaviors[$name])) {
            $this->_behaviors[$name]->detach();
        }
        $behavior->attach($this);
        $this->_behaviors[$name] = $behavior;
    }

    return $behavior;
}

在第一个if分支内判断 $behavior 是否为 行为类Behavior的一个对象,如果不是则$behavior肯定是一些配置,那根据这些配置得到相关行为的对象。

总之 $behavior 已经是一个行为对象了,我们先看函数体最后一行,可以知道此函数返回了这个对象。

接下来我们来看第二个if分支。

if (is_int($name)) {
    $behavior->attach($this);
    $this->_behaviors[] = $behavior;
} else {
    if (isset($this->_behaviors[$name])) {
        $this->_behaviors[$name]->detach();
    }
    $behavior->attach($this);
    $this->_behaviors[$name] = $behavior;
}

首先说对于 is_int($name) 的判断,还记得我们在绑定行为的时候么(传送门),在 behaviors() 返回的数组中,我们可以不为某个行为起名字,那叫做匿名指定,那自然这个key会是一个递增的数字,所以 is_int($name) 在判断是否为匿名行为。

如果是匿名行为,首先 $behavior->attach($this),然后放到 _behaviors 数组中。

如果不是匿名行为,先看看 _behaviors 数组中是否存在,如果存在则先 detach()后 $behavior->attach($this),然后放到 _behaviors 数组中。

这样一圈下来,_behaviors 数组中存放一群行为对象,有些是匿名的,有些是有名字的。对吧。

那么现在我们已经知道 attachBehaviorInternal函数的第一个功能 --- 填充 _behaviors 数组,反过来回顾 ensureBehaviors的作用,这个ensureBehaviors的一个功能就是确保 _behaviors 数组中有该组件应该有的所有行为对象。

为什么是第一个那???因为在 attachBehaviorInternal中我们发现除了填充数组外,还有一个叫做 $behavior->attach($this);的函数,它也将成为 attachBehaviorInternal / ensureBehaviors 功能之一。

那么 attach() 函数做了什么那?

attach()

先看一看它的代码,它在 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);
    }
}

分析一下,在组件处理自己行为的时候,将$this传递给了行为对象的方法 $behavior->attach($this),而在行为的 attach 方法中 $this->owner = $owner 一下,这意为着什么?

组件的每个行为对象都有一个属性owner存放了使用他们的组件对象,到此刻组件有 _behaviors 数组存放自己的所有行为对象,而行为有owner属性存放使用了自己的组件对象,它们建立了双向联系。

而关于在attach中的foreach循环体主要是处理事件的,我们会在行为和事件一篇说明。

此刻,我们再来归纳一下 ensureBehaviors 的功能,也就是绑定方法背后都触发了哪些动作

  1. 我们在组件的子类(比如AR、控制器等)中使用behaviors()来绑定一些行为。

  2. 然后有一个叫做 ensureBehaviors 的函数确保了此组件对象和绑定的行为对象可以彼此拥有。

但是

我们都知道,绑定行为后,组件对象就可以像使用自身属性和方法一样操作,这似乎和 ensureBehaviors 没有啥关系,下篇将为你解析当我们直接调用行为属性的时候,发生了什么?以及在这其中 ensureBehaviors 起到了什么作用?

阅读 2.2k

阿北哥ya
阿北,一名独立开发者、讲师和奶爸。本专栏主要分享一些技术文章和独立开发经验。
4.1k 声望
909 粉丝
0 条评论
4.1k 声望
909 粉丝
文章目录
宣传栏