[TOC]
# 簡介
使用行為(behavior)可以在不修改現有類的情況下,對類的功能進行擴充。 通過將行為綁定到一個類,可以使類具有行為本身所定義的屬性和方法,就好像類本來就有這些屬性和方法一樣。 而且不需要寫一個新的類去繼承或包含現有類。
Yii中的行為,其實是yii\\base\\Behavior類的實例, 只要將一個Behavior實例綁定到任意的yii\\base\\Component實例上, 這個Component就可以擁有該Behavior所定義的屬性和方法了。而如果將行為與事件關聯起來,可以玩的花樣就更多了。
但有一點需要注意,Behavior只能與Component類綁定。 他們是天生的一對,愛情不是你想買,想買就能買的,必要的物質是少不了的,奮斗吧少年。 所以,如果你寫了一個類,需要使用到行為,那么就果斷地繼承自yii\\base\\Component。
同時,行為單獨靠Behavior一方是實現不了的,就好像愛情不是一廂情愿。 為了支持Behavior,Yii對于yii\\base\\Component也進行了精心設計,這兩者共同配合,才有了神奇的行為。
# 使用行為
一個綁定了行為的類,表現起來是這樣的:
~~~
// Step 1: 定義一個將綁定行為的類
class MyClass extends yii\base\Component
{
// 空的
}
// Step 2: 定義一個行為類,他將綁定到MyClass上
class MyBehavior extends yii\base\Behavior
{
// 行為的一個屬性
public $property1 = 'This is property in MyBehavior.';
// 行為的一個方法
public function method1()
{
return 'Method in MyBehavior is called.';
}
}
$myClass = new MyClass();
$myBehavior = new MyBehavior();
// Step 3: 將行為綁定到類上
$myClass->attachBehavior('myBehavior', $myBehavior);
// Step 4: 訪問行為中的屬性和方法,就和訪問類自身的屬性和方法一樣
echo $myClass->property1;
echo $myClass->method1();
~~~
上面的代碼你不用全都看懂,雖然你可能已經用腳趾頭猜到了這些代碼的意思, 但這里你只需要記住行為中的屬性和方法可以被所綁定的類像訪問自身的屬性和方法一樣直接訪問就OK了。 代碼中, $myClass 是沒有 property1 method() 成員的。這倆是 $myBehavior 的成員。 但是,通過 attachBehavior() 將行為綁定到對象之后, $myCalss 就好像練成了吸星大法、化功大法,表現的財大氣粗,將別人的屬性和方法都變成了自己的。
另外,從上面的代碼中,你還要掌握使用行為的大致流程:
* 從 `yii\base\Component` 派生自己的類,以便使用行為;
* 從 `yii\base\Behavior` 派生自己的行為類,里面定義行為涉及到的屬性、方法;
* 將Component和Behavior綁定起來;
* 像使用Component自身的屬性和方法一樣,盡情使用行為中定義的屬性和方法。
# 行為的要素
我們提到了行為只是 yii\base\Behavior 類的實例。 那么這個類究竟有什么秘密呢?其實說破了也沒有什么的他只是一個簡單的封裝而已,非常的簡單:
~~~
class Behavior extends Object
{
// 指向行為本身所綁定的Component對象
public $owner;
// Behavior 基類本身沒用,主要是子類使用,重載這個函數返回一個數組表
// 示行為所關聯的事件
public function events()
{
return [];
}
// 綁定行為到 $owner
public function attach($owner)
{
... ...
}
// 解除綁定
public function detach()
{
... ...
}
}
~~~
這就是Behavior的全部代碼了,是不是很簡單?Behavior類的要素的確很簡單:
* `$owner` 成員變量,用于指向行為的依附對象;
* events() 用于表示行為所有要響應的事件;
* attach() 用于將行為與Component綁定起來;
* deatch() 用于將行為從Component上解除。
## 行為的依附對象
yii\\base\\Behavior::$owner指向的是Behavior實例本身所依附的對象。這是行為中引用所依附對象的唯一手段了。 通過這個$owner,行為才能訪問所依附的Component,才能將本身的方法作為事件handler綁定到Component上。
$owner由yii\\base\\Behavior::attach()進行賦值。 也就是在將行為綁定到某個Component時,$owner就已經名花有主了。 一般情況下,不需要你自己手動去指定$owner的值, 在調用yii\\base\\Componet::attachBehavior()將行為與對象綁定時, Component會自動地將$this作為參數,調用yii\\base\\Behavior::attach()。
有一點需要格外注意,由于行為從本質來講是一個PHP類,其方法就是類方法,就是成員函數。 所以,在行為的方法中,$this引用的是行為本身, 試圖通過$this來訪問行為所依附的Component是行不通的。 正確的方法是通過yii\\base\\Behavior::$owner來訪問Component。
## 行為所要響應的事件
行為與事件結合后,可以在不對類作修改的情況下,補充類在事件觸發后的各種不同反應。 為此,只需要重載 yii\base\Behavior::events() 方法,表示這個行為將對類的何種事件進行何種反饋即可:
~~~
namespace app\Components;
use yii\db\ActiveRecord;
use yii\base\Behavior;
class MyBehavior extends Behavior
{
// 重載events() 使得在事件觸發時,調用行為中的一些方法
public function events()
{
// 在EVENT_BEFORE_VALIDATE事件觸發時,調用成員函數 beforeValidate
return [
ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
];
}
// 注意beforeValidate 是行為的成員函數,而不是綁定的類的成員函數。
// 還要注意,這個函數的簽名,要滿足事件handler的要求。
public function beforeValidate($event)
{
// ...
}
}
~~~
上面的代碼中, events() 返回一個數組,表示所要做出響應的事件, 上例中的事件是 `ActiveRecord::EVENT_BEFORE_VALIDATE` ,以數組的鍵來表示, 而數組的值則表示做好反應的事件handler,上例中是 beforeValidate() ,事件handler可以是以下形式:
字符串,表示行為類的方法,如上面的例就是這種情況。 這個是與事件handler不同的,事件handler中使用字符串時,是表示PHP全局函數,而這里表示行為類內部的方法。
一個對象或類的成員函數,以數組的形式,如 [$object, 'methodName'] 。這個與事件handler是一致的。
一個匿名函數。
對于事件響應函數的簽名,要求與事件handler一樣:
~~~
function ($event) { }
~~~
## 行為的綁定與解除
說到綁定與解除,這意味著這個事情有2方,行為和Component。單獨一方是沒有綁定或解除的說法的。 因此,這里我們先賣一關子,等后面講綁定和解除的原理時,再來講有關的內容。
這里你只需要知道,對于綁定和解除,Behavior 分別使用attach()和detach()來實現就OK了
# 定義一個行為
定義一個行為,就是準備好要注入到現有類中去的屬性和方法, 這些屬性和方法要寫到一個yii\\base\\Behavior類中。 所以,定義一個行為,就是寫一個 Behavior的子類,子類中包含了所要注入的屬性和方法
~~~
namespace app\Components;
use yii\base\Behavior;
class MyBehavior extends Behavior
{
public $prop1;
private $_prop2;
private $_prop3;
private $_prop4;
public function getProp2()
{
return $this->_prop2;
}
public function setProp3($value)
{
$this->_prop3 = $value;
}
public function foo()
{
// ...
}
protected function bar()
{
// ...
}
}
~~~
上面的代碼通過定義一個 app\Components\MyBehavior 類而定義一個行為。 由于 MyBehavior 繼承自 yii\base\Behavior 從而間接地繼承自 yii\base\Object 。 沒錯,這是我們的老朋友了。因此,這個類有一個public的成員變量 prop1 , 一個只讀屬性 prop2 ,一個只寫屬性 prop3 ,一個public的方法 foo() 。 另外,還有一個private 的成員變量 $_prop4 ,一個protected 的方法 bar() 。 如果你不清楚只讀屬性和只寫屬性,最好回頭看看 屬性(Property) 部分的內容。
當這MyBehavior與一個Component綁定后, 綁定的Component也就擁有了 prop1 prop2 這兩個屬性和方法 foo() ,因為他們都是 public 的。 而 private 的 $_prop4 和 protected 的 bar 就得不到了。 至于原因么,后面講行為注入的原理時,我們再解釋
## 行為的綁定
行為的綁定通常是由Component來發起。有兩種方式可以將一個Behavior綁定到一個 yii\base\Component 。 一種是靜態的方法,另一種是動態的。靜態的方法在實踐中用得比較多一些。 因為一般情況下,在你的代碼沒跑起來之前,一個類應當具有何種行為,是確定的。 動態綁定的方法主要是提供了更靈活的方式,但實際使用中并不多見。
## 靜態方法綁定行為
靜態綁定行為,只需要重載 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',
],
]
~~~
具體參見 配置項(Configuration) 部分的內容
## 動態方法綁定行為
動態綁定行為,需要調用 `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);
}
}
~~~
上面的代碼干了兩件事:
* 設置好行為的 $owner ,使得行為可以訪問、操作所依附的對象
* 遍歷行為中的 events() 返回的數組,將準備響應的事件,通過所依附類的 on() 綁定到類上
說了這么多,關于綁定,做個小結:
* 綁定的動作從Component發起;
* 靜態綁定通過重載 yii\base\Componet::behaviors() 實現;
* 動態綁定通過調用 yii\base\Component::attachBehaviors() 實現;
* 行為還可以通過為 Component 配置 as 配置項進行綁定;
* 行為有匿名行為和命名行為之分,區別在于綁定時是否給出命名。 命名行為可以通過其命名進行標識,從而有針對性地進行解除等操作
* 綁定過程中,后綁定的行為會取代已經綁定的同名行為;
* 綁定的意義有兩點,一是為行為設置 $owner 。二是將行為中擬響應的事件的handler綁定到類中去。
## 解除行為
解除行為只需調用 `yii\base\Component::detachBehavior()` 就OK了:
~~~
$Component->detachBehavior('myBehavior2');
~~~
這樣就可以解除已經綁定好的名為 myBehavior2 的行為了。 但是,對于匿名行為,這個方法就無從下手了。不過我們可以一不做二不休,解除所有綁定好的行為:
~~~
$Component->detachBehaviors();
~~~
這上面兩種方法,都會調用到 yii\base\Behavior::detach() ,其代碼如下:
~~~
public function detach()
{
// 這得是個名花有主的行為才有解除一說
if ($this->owner) {
// 遍歷行為定義的事件,一一解除
foreach ($this->events() as $event => $handler) {
$this->owner->off($event, is_string($handler) ? [$this,
$handler] : $handler);
}
$this->owner = null;
}
}
~~~
與 `yii\base\Behavior::attach()` 相反,解除的過程就是干兩件事: 一是將 `$owner` 設置為 null ,表示這個行為沒有依附到任何類上。 二是通過Component的 off() 將綁定到類上的事件hanlder解除下來。一句話,善始善終。
- 目錄
- 配置
- 簡介
- 別名
- gii
- 配置項
- 模型
- 簡介
- 增刪改查
- AR和model
- 模型事件
- 場景
- query查詢
- 增刪改
- AR查詢器
- 模型關系定義
- AR模型連表查詢
- fields
- where拼接
- 模塊
- 創建模塊
- 控制器
- 表單
- 跳轉
- 響應
- 驗證器
- Action
- 組件
- url
- 分頁
- 驗證碼
- 緩存
- 文件上傳
- 預啟動組件
- 事件
- 自定義組件
- redis
- 日志
- 行為
- cookie和session
- 基礎知識
- 創建一個類
- 配置一個類
- object基類
- component組件類特性
- phpstorm無法更改php等級
- url地址美化
- 過濾器
- 請求處理
- 請求組件
- 響應組件
- header
- 用戶登錄
- 實現IdentityInterface接口
- 登錄
- 自動檢測登錄
- 獲取用戶信息
- 訪問行為追蹤
- phpstorm+postman斷點調試