概念理解:使用行爲(behavior)能夠在不修改現有類的狀況下,對類的功能進行擴充。 經過將行爲綁定到一個類,可使類具備行爲自己所定義的屬性和方法,就好像類原本就有這些屬性和方法同樣。 而不須要寫一個新的類去繼承或包含現有類。在功能上相似於 Traits ,達到相似於多繼承的目的。
<?php namespace common\components; use yii\base\Component; // 待綁定行爲的類 class MyClass extends Component { } <?php namespace common\components; use yii\base\Behavior; // 定義一個行爲類 class BehaviorTest extends Behavior { const EVENT_AFTER_SAVE = 'eventAfterAttach'; public $_val = '我是BehaviorTest裏面的公有屬性_val'; public function getOutput() { echo '我是BehaviorTest裏面的公有方法getOutput'; } public function events() { return [ self::EVENT_AFTER_SAVE => 'afterAttach' ]; } public function afterAttach() { echo '事件已觸發'; } } // 在控制器或者命令行下調用 $myClass = new MyClass(); $myBehavior = new BehaviorTest(); // 將行爲綁定到 MyClass 的實例 $myClass->attachBehavior('test', $myBehavior); // MyClass 實例調用行爲類中的屬性 echo $myClass->_val; // MyClass 實例調用行爲類中的方法 $myClass->getOutput(); // MyClass 實例觸發行爲類中定義的事件 $myClass->trigger(BehaviorTest::EVENT_AFTER_SAVE);
咱們先來看一下 $myClass->attachBehavior('test', $myBehavior);
行爲綁定的時候作了哪些事情?好的,又是咱們的老朋友yii\base\Component
了php
// yii\base\Component 的部分代碼 private $_behaviors; public function behaviors() { return []; } public function attachBehavior($name, $behavior) { $this->ensureBehaviors(); return $this->attachBehaviorInternal($name, $behavior); } public function ensureBehaviors() { if ($this->_behaviors === null) { $this->_behaviors = []; foreach ($this->behaviors() as $name => $behavior) { $this->attachBehaviorInternal($name, $behavior); } } } 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; }
$myClass
調用 attachBehavior()
傳入倆個參數,一個是行爲名稱,另外一個是行爲類的名稱或實例或者是數組,接着 attachBehavior()
調用了 ensureBehaviors()
,這個函數咱們暫時用不到,由於咱們沒有在MyClass
裏面重載behaviors()
,不過也能大概猜到ensureBehaviors()
的用途了。再往下調用的是私有函數attachBehaviorInternal()
,這個函數先判斷傳進來的$behavior
是否已實例化,若是尚未則進行實例化,再經過$name
判斷是匿名行爲仍是命名行爲,若是是命名行爲,須要查看是否已經綁定同名的行爲,若是綁定了同名的行爲,會將之前的行爲先解綁再調用$behavior->attach($this);
[注:這裏$this
指的MyClass
實例,也就是$myClass
],這樣咱們就來到了yii\base\Behavior
的attach()
方法,下面是attach()
方法的源碼:html
public function attach($owner) { $this->owner = $owner; foreach ($this->events() as $event => $handler) { $owner->on($event, is_string($handler) ? [$this, $handler] : $handler); } }
$this->owner = $owner;
[注:這裏$this
指的是$behavior
也就是類BehaviorTest
的實例],這句代碼指定了行爲類的主人是誰,後面的代碼看着彷佛似曾相識?是的,就是將行爲類events()
方法裏面的事件也綁定的宿主身上,這裏不具體展開,有興趣的小夥伴能夠看一下淺析Yii2.0的事件Event。最後將行爲名稱和行爲實例放到$myClass
的屬性_behavior
中,至此,行爲的綁定就結束了。好像也沒幹什麼啊,咱們如今能夠打印一下$myClass
的數據結構是怎樣的?segmentfault
common\components\MyClass Object ( [_events:yii\base\Component:private] => Array ( [eventAfterAttach] => Array ( [0] => Array ( [0] => Array ( [0] => common\components\BehaviorTest Object ( [_val] => 我是BehaviorTest裏面的公有屬性_val [owner] => common\components\MyClass Object *RECURSION* ) [1] => afterAttach ) [1] => ) ) ) [_eventWildcards:yii\base\Component:private] => Array ( ) [_behaviors:yii\base\Component:private] => Array ( [test] => common\components\BehaviorTest Object ( [_val] => 我是BehaviorTest裏面的公有屬性_val [owner] => common\components\MyClass Object *RECURSION* ) ) )
能夠看到$myClass
已經綁定了一個行爲test
,綁定了一個事件eventAfterAttach
,那麼綁定行爲之後,是怎麼調用行爲類裏面的屬性和方法呢?數組
仍是看一下demo裏面$myClass->_val
這句代碼是怎麼執行的?根據上面$myClass
的數據結構能夠看出並無_val
這個屬性,可是yii\base\Component
裏面實現了__get()
這個魔術方法,咱們看一下源碼。數據結構
public function __get($name) { $getter = 'get' . $name; if (method_exists($this, $getter)) { // read property, e.g. getName() return $this->$getter(); } // behavior property $this->ensureBehaviors(); foreach ($this->_behaviors as $behavior) { if ($behavior->canGetProperty($name)) { return $behavior->$name; } } if (method_exists($this, 'set' . $name)) { throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name); } throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name); }
又似曾相識?是的,跟yii\base\BaseObject
裏面屬性的實現相似,有興趣的小夥伴能夠看一下淺析Yii2.0的屬性Property。而後直接看註釋behavior property
部分,又去調用了ensureBehaviors()
,先無論,接着又去遍歷_behaviors
這個屬性,根據上面$myClass
的數據結構得知,此時foreach裏面的$behavior
就是行爲類common\components\BehaviorTest
實例,先經過canGetProperty
判斷_val
是否可讀或者存在,你們能夠去看yii\base\BaseObject
裏面該方法的實現。咱們這裏返回的是true
,而後就直接經過common\components\BehaviorTest
的實例$behavior
返回_val
的值。yii
根據上面獲取行爲類裏面屬性的流程咱們注意到:函數
protected
或者是private
是獲取不到的。Component
綁定了多個行爲,而且多個行爲中有同名的屬性,那麼該Component
獲取的是第一個行爲類裏面的該屬性。那麼行爲類裏面的方法是怎麼被調用的呢?屬性的調用是經過__get()
來實現的,很容易聯想到方法的調用是經過__call()
來實現的,咱們查看一下yii\base\BaseObject
源碼,果真裏面實現__call()
這個魔術方法,下面是源碼,而後對照上面$myClass
的數據結構一看就明白了。須要注意的是,跟上面屬性的調用同樣,方法也必須是public
的,protected 、private
方法是調用不了的。this
public function __call($name, $params) { $this->ensureBehaviors(); foreach ($this->_behaviors as $object) { if ($object->hasMethod($name)) { return call_user_func_array([$object, $name], $params); } } throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()"); }
注意到__call()
裏面又有一個老朋友ensureBehaviors()
這個函數彷佛無處不在?是的,查看一下yii\base\Component
裏面的源碼咱們能夠發現全部的公有方法都調用了這個函數,那麼這個函數究竟是幹嗎的呢,其實demo
裏面綁定行爲的方式能夠稱爲主動綁定,就是咱們主動調用函數attachBehavior()
去綁定行爲的,對應的就是被動綁定了,實現方式就是在待綁定行爲的類裏面重載behaviors()
這個函數就能夠實現綁定了,至關於一個行爲的配置項。倆種綁定方式看我的喜愛了,若是一個類須要綁定的行爲很明確,推薦使用配置項的方法去綁定,也就是被動綁定。下面是將demo
裏面的綁定方式改爲被動綁定。spa
<?php namespace common\components; use yii\base\Component; class MyClass extends Component { public function behaviors() { return [ 'test' => new BehaviorTest() ]; } } 此時能夠將demo中 $myClass->attachBehavior('test', $myBehavior); 這句代碼去掉,$myClass也是一樣能夠調用類 BehaviorTest 裏面的屬性和方法
這倆天查看了Yii2.0
事件、行爲的實現方式,以爲有不少類似的地方,都是經過yii\base\Component
來實現的,經過打印的數據結構也能夠看到,Component
主要就是圍繞_events _eventWildcards _behaviors
這三個屬性展開的,其中第二個屬性是 事件的通配符模式,也能夠歸屬到 事件中,那麼這樣Component
的主要功能就是就是實現了 事件和行爲。而且實現原理上也是類似的,都是往Component
裏面綁定事件和行爲的handle
,而後觸發事件或者行爲的時候,再去回調相應的handle
。不過在解除的時候雖然都是刪掉相應的handle
,可是解除行爲還須要解除在綁定行爲的時候綁定的事件,這點不太同樣。命令行
以上總結參考了深刻理解Yii2.0,其實之前就看過,可是也只是侷限於看過,沒有本身跑demo調試、查看源代碼,而後就誤覺得本身明白了,其實過倆天什麼都不記得了。因此如今經過寫博客來加深本身的理解,因爲水平有限,歡迎小夥伴交流和指正。