不知不觉已经发布了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 的功能,也就是绑定方法背后都触发了哪些动作
我们在组件的子类(比如AR、控制器等)中使用behaviors()来绑定一些行为。
然后有一个叫做 ensureBehaviors 的函数确保了此组件对象和绑定的行为对象可以彼此拥有。
但是
我们都知道,绑定行为后,组件对象就可以像使用自身属性和方法一样操作,这似乎和 ensureBehaviors 没有啥关系,下篇将为你解析当我们直接调用行为属性的时候,发生了什么?以及在这其中 ensureBehaviors 起到了什么作用?