不知不覺已經發布了7篇關於yii2行為的文章。傳送門,今天再分享一篇到掘金專欄。
為何使用 yii\base\Component::behaviors() 就能繫結行為,發生了什麼?
我們先來窺視一下類 Component 內部和繫結行為相關的函式。
- yii\base\Component::behaviors()
- yii\base\Component::ensureBehaviors()
- yii\base\Component::attachBehaviorInternal()
- yii\base\Behavior::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 起到了什麼作用?