定義行為
要定義行為,通過繼承 yii\base\Behavior 或其子類來建立一個類。如:
namespace app\components;
use yii\base\Behavior;
class MyBehavior extends Behavior
{
public $prop1;
private $_prop2;
public function getProp2()
{
return $this->_prop2;
}
public function setProp2($value)
{
$this->_prop2 = $value;
}
public function foo()
{
// ...
}
}
以上代碼定義了行為類 app\components\MyBehavior 並為要附加行為的組件提供了兩個屬性 prop1 、 prop2 和一個方法 foo()。注意屬性 prop2 是通過 getter getProp2() 和 setter setProp2() 定義的。能這樣用是因為 yii\base\Object 是 yii\base\Behavior 的祖先類,此祖先類支持用 getter 和 setter 方法定義屬性
提示:在行為內部可以通過 yii\base\Behavior::owner 屬性訪問行為已附加的組件。
靜態方法綁定行為
靜態綁定行為,只需要重載 yii\base\Component::behaviors() 就可以了。 這個方法用於描述類所具有的行為。如何描述呢? 使用配置來描述,可以是Behavior類名,也可以是Behavior類的配置數組:
namespace app\models;
use yii\db\ActiveRecord;
use app\Components\MyBehavior;
class User extends ActiveRecord
{
public function behaviors()
{
return [
// 匿名的行為,僅直接給出行為的類名稱
MyBehavior::className(),
// 名為myBehavior2的行為,也是僅給出行為的類名稱
'myBehavior2' => MyBehavior::className(),
// 匿名行為,給出了MyBehavior類的配置數組
[
'class' => MyBehavior::className(),
'prop1' => 'value1',
'prop3' => 'value3',
],
// 名為myBehavior4的行為,也是給出了MyBehavior類的配置數組
'myBehavior4' => [
'class' => MyBehavior::className(),
'prop1' => 'value1',
'prop3' => 'value3',
]
];
}
}
還有一個靜態的綁定辦法,就是通過配置文件來綁定:
[
'as myBehavior2' => MyBehavior::className(),
'as myBehavior3' => [
'class' => MyBehavior::className(),
'prop1' => 'value1',
'prop3' => 'value3',
],
]
動態方法綁定行為
動態綁定行為,需要調用 yii\base\Compoent::attachBehaviors():
$Component->attachBehaviors([ 'myBehavior1' => new MyBehavior, // 這是一個命名行為 MyBehavior::className(), // 這是一個匿名行為 ]);
這個方法接受一個數組參數,參數的含義與上面靜態綁定行為是一樣一樣的。
在上面的這些例子中,以數組的鍵作為行為的命名,而對於沒有提供鍵名的行為,就是匿名行為。
對於命名的行為,可以調用 yii\base\Component::getBehavior() 來取得這個綁定好的行為:
$behavior = $Component->getBehavior('myBehavior2');
對於匿名的行為,則沒有辦法直接引用了。但是,可以獲取所有的綁定好的行為:
$behaviors = $Component->getBehaviors();
綁定的內部原理
只是重載一個 yii\base\Component::behaviors() 就可以這麼神奇地使用行為了? 這只是冰山的一角,實際上關系到綁定的過程,有關的方面有:
yii\base\Component::behaviors() yii\base\Component::ensureBehaviors() yii\base\Component::attachBehaviorInternal() yii\base\Behavior::attach()
4個方法中,Behavior只占其一,更多的代碼,是在Component中完成的。
yii\base\Component::behaviors() 上面講靜態方法綁定行為時已經提到了,就是返回一個數組用於描述行為。 那麼 yii\base\Component::ensuerBehaviors() 呢?
這個方法會在Component的諸多地方調用 __get() __set() __isset() __unset() __call() canGetProperty() hasMethod() hasEventHandlers() on() off() 等用到,看到這麼多是不是頭疼?一點都不復雜,一句話,只要涉及到類的屬性、方法、事件這個函數都會被調用到。
這麼眾星拱月,被諸多凡人所需要的 ensureBehaviors() 究竟是何許人也? 就像名字所表明的,他的作用在於“ensure” 。其實只是確保 behaviors() 中所描述的行為已經進行了綁定而已:
public function ensureBehaviors()
{
// 為null表示尚未綁定
// 多說一句,為空數組表示沒有綁定任何行為
if ($this->_behaviors === null) {
$this->_behaviors = [];
// 遍歷 $this->behaviors() 返回的數組,並綁定
foreach ($this->behaviors() as $name => $behavior) {
$this->attachBehaviorInternal($name, $behavior);
}
}
}
這個方法主要是對子類用的, yii\base\Compoent 沒有任何預先注入的行為,所以,這個調用沒有用。 但是對於子類,你可能重載了 yii\base\Compoent::behaviros() 來預先注入一些行為。 那麼,這個函數會將這些行為先注入進來。
從上面的代碼中,自然就看到了接下來要說的第三個東東, yii\base\Component\attachBehaviorInternal():
private function attachBehaviorInternal($name, $behavior)
{
// 不是 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;
}
首先要注意到,這是一個private成員。其實在Yii中,所有後綴為 *Internal 的方法,都是私有的。 這個方法干了這麼幾件事:
如果 $behavior 參數並非是一個 Behavior 實例,就以之為參數,用 Yii::createObject() 創建出來。
如果以匿名行為的形式綁定行為,那麼直接將行為附加在這個類上。
如果是命名行為,先看看是否有同名的行為已經綁定在這個類上,如果有,用後來的行為取代之前的行為。
在 yii\base\Component::attachBehaviorInternal() 中, 以 $this 為參數調用了 yii\base\Behavior::attach() 。 從而,引出了跟綁定相關的最後一個家伙 yii\base\Behavior::attach() , 這也是前面我們講行為的要素時沒講完的。先看看代碼:
public function attach($owner)
{
$this->owner = $owner;
foreach ($this->events() as $event => $handler) {
$owner->on($event, is_string($handler) ? [$this, $handler] :
$handler);
}
}
上面的代碼干了兩件事:
總結
說了這麼多,關於綁定,做個小結: