本文實例講述了YII2.0框架行為(Behavior)。分享給大家供大家參考,具體如下:
行為(Behavior)
使用行為(behavior)可以在不修改現(xiàn)有類的情況下,對類的功能進(jìn)行擴(kuò)充。 通過將行為綁定到一個類,可以使類具有行為本身所定義的屬性和方法,就好像類本來就有這些屬性和方法一樣。 而且不需要寫一個新的類去繼承或包含現(xiàn)有類。
Yii中的行為,其實是 yii\base\Behavior 類的實例, 只要將一個Behavior實例綁定到任意的 yii\base\Component 實例上, 這個Component就可以擁有該Behavior所定義的屬性和方法了。而如果將行為與事件關(guān)聯(lián)起來,可以玩的花樣就更多了。
但有一點需要注意,Behavior只能與Component類綁定。 他們是天生的一對,愛情不是你想買,想買就能買的,必要的物質(zhì)是少不了的,奮斗吧少年。 所以,如果你寫了一個類,需要使用到行為,那么就果斷地繼承自yii\base\Component 。
同時,行為單獨(dú)靠Behavior一方是實現(xiàn)不了的,就好像愛情不是一廂情愿。 為了支持Behavior,Yii對于yii\base\Component 也進(jìn)行了精心設(shè)計,這兩者共同配合,才有了神奇的行為。
使用行為
一個綁定了行為的類,表現(xiàn)起來是這樣的:
// 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();
上面的代碼你不用全都看懂,雖然你可能已經(jīng)用腳趾頭猜到了這些代碼的意思, 但這里你只需要記住行為中的屬性和方法可以被所綁定的類像訪問自身的屬性和方法一樣直接訪問就OK了。 代碼中, $myClass 是沒有property1
method()
成員的。這倆是 $myBehavior 的成員。 但是,通過 attachBehavior()
將行為綁定到對象之后, $myCalss 就好像練成了吸星大法、化功大法,表現(xiàn)的財大氣粗,將別人的屬性和方法都變成了自己的。
另外,從上面的代碼中,你還要掌握使用行為的大致流程:
- 從 yii\base\Component 派生自己的類,以便使用行為;
- 從 yii\base\Behavior 派生自己的行為類,里面定義行為涉及到的屬性、方法;
- 將Component和Behavior綁定起來;
- 像使用Component自身的屬性和方法一樣,盡情使用行為中定義的屬性和方法。
行為的要素
我們提到了行為只是 yii\base\Behavior 類的實例。 那么這個類究竟有什么秘密呢?其實說破了也沒有什么的他只是一個簡單的封裝而已,非常的簡單:
class Behavior extends Object
{
// 指向行為本身所綁定的Component對象
public $owner;
// Behavior 基類本身沒用,主要是子類使用,重載這個函數(shù)返回一個數(shù)組表
// 示行為所關(guān)聯(lián)的事件
public function events()
{
return [];
}
// 綁定行為到 $owner
public function attach($owner)
{
... ...
}
// 解除綁定
public function detach()
{
... ...
}
}
這就是Behavior的全部代碼了,是不是很簡單?Behavior類的要素的確很簡單:
- $owner 成員變量,用于指向行為的依附對象;
events()
用于表示行為所有要響應(yīng)的事件;
attach()
用于將行為與Component綁定起來;
deatch()
用于將行為從Component上解除。
下面分別進(jìn)行講解。
行為的依附對象
yii\base\Behavior::$owner
指向的是Behavior實例本身所依附的對象。這是行為中引用所依附對象的唯一手段了。 通過這個 $owner ,行為才能訪問所依附的Component,才能將本身的方法作為事件handler綁定到Component上。
$owner 由 yii\base\Behavior::attach()
進(jìn)行賦值。 也就是在將行為綁定到某個Component時, $owner 就已經(jīng)名花有主了。 一般情況下,不需要你自己手動去指定 $owner 的值, 在調(diào)用 yii\base\Componet::attachBehavior()
將行為與對象綁定時, Component會自動地將 $this 作為參數(shù),調(diào)用 yii\base\Behavior::attach()
。
有一點需要格外注意,由于行為從本質(zhì)來講是一個PHP類,其方法就是類方法,就是成員函數(shù)。 所以,在行為的方法中, $this 引用的是行為本身, 試圖通過 $this 來訪問行為所依附的Component是行不通的。 正確的方法是通過 yii\base\Behavior::$owner
來訪問Component。
行為所要響應(yīng)的事件
行為與事件結(jié)合后,可以在不對類作修改的情況下,補(bǔ)充類在事件觸發(fā)后的各種不同反應(yīng)。 為此,只需要重載yii\base\Behavior::events()
方法,表示這個行為將對類的何種事件進(jìn)行何種反饋即可:
namespace app\Components;
use yii\db\ActiveRecord;
use yii\base\Behavior;
class MyBehavior extends Behavior
{
// 重載events() 使得在事件觸發(fā)時,調(diào)用行為中的一些方法
public function events()
{
// 在EVENT_BEFORE_VALIDATE事件觸發(fā)時,調(diào)用成員函數(shù) beforeValidate
return [
ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
];
}
// 注意beforeValidate 是行為的成員函數(shù),而不是綁定的類的成員函數(shù)。
// 還要注意,這個函數(shù)的簽名,要滿足事件handler的要求。
public function beforeValidate($event)
{
// ...
}
}
上面的代碼中, events()
返回一個數(shù)組,表示所要做出響應(yīng)的事件, 上例中的事件是ActiveRecord::EVENT_BEFORE_VALIDATE
,以數(shù)組的鍵來表示, 而數(shù)組的值則表示做好反應(yīng)的事件handler,上例中是beforeValidate()
,事件handler可以是以下形式:
- 字符串,表示行為類的方法,如上面的例就是這種情況。 這個是與事件handler不同的,事件handler中使用字符串時,是表示PHP全局函數(shù),而這里表示行為類內(nèi)部的方法。
- 一個對象或類的成員函數(shù),以數(shù)組的形式,如 [$object, 'methodName'] 。這個與事件handler是一致的。
- 一個匿名函數(shù)。
對于事件響應(yīng)函數(shù)的簽名,要求與事件handler一樣:
具體內(nèi)容,請參考 事件(Event) 的內(nèi)容。
行為的綁定與解除
說到綁定與解除,這意味著這個事情有2方,行為和Component。單獨(dú)一方是沒有綁定或解除的說法的。 因此,這里我們先賣一關(guān)子,等后面講綁定和解除的原理時,再來講有關(guān)的內(nèi)容。
這里你只需要知道,對于綁定和解除,Behavior 分別使用 attach()
和 detach()
來實現(xiàn)就OK了。
定義一個行為
定義一個行為,就是準(zhǔn)備好要注入到現(xiàn)有類中去的屬性和方法, 這些屬性和方法要寫到一個 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) 部分的內(nèi)容。
當(dāng)這MyBehavior與一個Component綁定后, 綁定的Component也就擁有了 prop1 prop2 這兩個屬性和方法foo()
,因為他們都是 public 的。 而 private 的 $_prop4 和 protected 的 bar 就得不到了。 至于原因么,后面講行為注入的原理時,我們再解釋。
行為的綁定
行為的綁定通常是由Component來發(fā)起。有兩種方式可以將一個Behavior綁定到一個 yii\base\Component 。 一種是靜態(tài)的方法,另一種是動態(tài)的。靜態(tài)的方法在實踐中用得比較多一些。 因為一般情況下,在你的代碼沒跑起來之前,一個類應(yīng)當(dāng)具有何種行為,是確定的。 動態(tài)綁定的方法主要是提供了更靈活的方式,但實際使用中并不多見。
靜態(tài)方法綁定行為
靜態(tài)綁定行為,只需要重載 yii\base\Component::behaviors()
就可以了。 這個方法用于描述類所具有的行為。如何描述呢? 使用配置來描述,可以是Behavior類名,也可以是Behavior類的配置數(shù)組:
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類的配置數(shù)組
[
'class' => MyBehavior::className(),
'prop1' => 'value1',
'prop3' => 'value3',
],
// 名為myBehavior4的行為,也是給出了MyBehavior類的配置數(shù)組
'myBehavior4' => [
'class' => MyBehavior::className(),
'prop1' => 'value1',
'prop3' => 'value3',
]
];
}
}
還有一個靜態(tài)的綁定辦法,就是通過配置文件來綁定:
[
'as myBehavior2' => MyBehavior::className(),
'as myBehavior3' => [
'class' => MyBehavior::className(),
'prop1' => 'value1',
'prop3' => 'value3',
],
]
具體參見配置項(Configuration) 部分的內(nèi)容。
動態(tài)方法綁定行為
動態(tài)綁定行為,需要調(diào)用 yii\base\Compoent::attachBehaviors()
:
$Component->attachBehaviors([
'myBehavior1' => new MyBehavior, // 這是一個命名行為
MyBehavior::className(), // 這是一個匿名行為
]);
這個方法接受一個數(shù)組參數(shù),參數(shù)的含義與上面靜態(tài)綁定行為是一樣一樣的。
在上面的這些例子中,以數(shù)組的鍵作為行為的命名,而對于沒有提供鍵名的行為,就是匿名行為。
對于命名的行為,可以調(diào)用 yii\base\Component::getBehavior()
來取得這個綁定好的行為:
$behavior = $Component->getBehavior('myBehavior2');
對于匿名的行為,則沒有辦法直接引用了。但是,可以獲取所有的綁定好的行為:
$behaviors = $Component->getBehaviors();
綁定的內(nèi)部原理
只是重載一個 yii\base\Component::behaviors()
就可以這么神奇地使用行為了? 這只是冰山的一角,實際上關(guān)系到綁定的過程,有關(guān)的方面有:
yii\base\Component::behaviors()
yii\base\Component::ensureBehaviors()
yii\base\Component::attachBehaviorInternal()
yii\base\Behavior::attach()
4個方法中,Behavior只占其一,更多的代碼,是在Component中完成的。
yii\base\Component::behaviors()
上面講靜態(tài)方法綁定行為時已經(jīng)提到了,就是返回一個數(shù)組用于描述行為。 那么yii\base\Component::ensuerBehaviors()
呢?
這個方法會在Component的諸多地方調(diào)用 __get()
__set()
__isset()
__unset()
__call()
canGetProperty()hasMethod()
hasEventHandlers()
on()
off()
等用到,看到這么多是不是頭疼?一點都不復(fù)雜,一句話,只要涉及到類的屬性、方法、事件這個函數(shù)都會被調(diào)用到。
這么眾星拱月,被諸多凡人所需要的 ensureBehaviors()
究竟是何許人也? 就像名字所表明的,他的作用在于“ensure” 。其實只是確保 behaviors()
中所描述的行為已經(jīng)進(jìn)行了綁定而已:
public function ensureBehaviors()
{
// 為null表示尚未綁定
// 多說一句,為空數(shù)組表示沒有綁定任何行為
if ($this->_behaviors === null) {
$this->_behaviors = [];
// 遍歷 $this->behaviors() 返回的數(shù)組,并綁定
foreach ($this->behaviors() as $name => $behavior) {
$this->attachBehaviorInternal($name, $behavior);
}
}
}
這個方法主要是對子類用的, yii\base\Compoent 沒有任何預(yù)先注入的行為,所以,這個調(diào)用沒有用。 但是對于子類,你可能重載了 yii\base\Compoent::behaviros()
來預(yù)先注入一些行為。 那么,這個函數(shù)會將這些行為先注入進(jìn)來。
從上面的代碼中,自然就看到了接下來要說的第三個東東, yii\base\Component\attachBehaviorInternal()
:
private function attachBehaviorInternal($name, $behavior)
{
// 不是 Behavior 實例,說是只是類名、配置數(shù)組,那么就創(chuàng)建出來吧
if (!($behavior instanceof Behavior)) {
$behavior = Yii::createObject($behavior);
}
// 匿名行為
if (is_int($name)) {
$behavior->attach($this);
$this->_behaviors[] = $behavior;
// 命名行為
} else {
// 已經(jīng)有一個同名的行為,要先解除,再將新的行為綁定上去。
if (isset($this->_behaviors[$name])) {
$this->_behaviors[$name]->detach();
}
$behavior->attach($this);
$this->_behaviors[$name] = $behavior;
}
return $behavior;
}
首先要注意到,這是一個private成員。其實在Yii中,所有后綴為 *Internal 的方法,都是私有的。 這個方法干了這么幾件事:
- 如果 $behavior 參數(shù)并非是一個 Behavior 實例,就以之為參數(shù),用
Yii::createObject()
創(chuàng)建出來。
- 如果以匿名行為的形式綁定行為,那么直接將行為附加在這個類上。
- 如果是命名行為,先看看是否有同名的行為已經(jīng)綁定在這個類上,如果有,用后來的行為取代之前的行為。
在 yii\base\Component::attachBehaviorInternal()
中, 以 $this 為參數(shù)調(diào)用了 yii\base\Behavior::attach()
。 從而,引出了跟綁定相關(guān)的最后一個家伙 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);
}
}
上面的代碼干了兩件事:
- 設(shè)置好行為的 $owner ,使得行為可以訪問、操作所依附的對象
- 遍歷行為中的
events()
返回的數(shù)組,將準(zhǔn)備響應(yīng)的事件,通過所依附類的 on()
綁定到類上
說了這么多,關(guān)于綁定,做個小結(jié):
- 綁定的動作從Component發(fā)起;
- 靜態(tài)綁定通過重載
yii\base\Componet::behaviors()
實現(xiàn);
- 動態(tài)綁定通過調(diào)用
yii\base\Component::attachBehaviors()
實現(xiàn);
- 行為還可以通過為 Component 配置 as 配置項進(jìn)行綁定;
- 行為有匿名行為和命名行為之分,區(qū)別在于綁定時是否給出命名。 命名行為可以通過其命名進(jìn)行標(biāo)識,從而有針對性地進(jìn)行解除等操作;
- 綁定過程中,后綁定的行為會取代已經(jīng)綁定的同名行為;
- 綁定的意義有兩點,一是為行為設(shè)置 $owner 。二是將行為中擬響應(yīng)的事件的handler綁定到類中去。
解除行為
解除行為只需調(diào)用 yii\base\Component::detachBehavior()
就OK了:
$Component->detachBehavior('myBehavior2');
這樣就可以解除已經(jīng)綁定好的名為 myBehavior2 的行為了。 但是,對于匿名行為,這個方法就無從下手了。不過我們可以一不做二不休,解除所有綁定好的行為:
$Component->detachBehaviors();
這上面兩種方法,都會調(diào)用到 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 設(shè)置為 null ,表示這個行為沒有依附到任何類上。 二是通過Component的 off()
將綁定到類上的事件hanlder解除下來。一句話,善始善終。
行為響應(yīng)的事件實例
上面的綁定和解除過程,我們看到Y(jié)ii費(fèi)了那么大勁,主要就是為了將行為中的事件handler綁定到類中去。 在實際編程時,行為用得最多的,也是對于Compoent各種事件的響應(yīng)。 通過行為注入,可以在不修改現(xiàn)有類的代碼的情況下,更改、擴(kuò)展類對于事件的響應(yīng)和支持。 使用這個技巧,可以玩出很炫的花樣。 而要將行為與Component的事件關(guān)聯(lián)起來,就要通過 yii\base\Behavior::events()
方法。
上面Behavior基類的代碼中,這個方法只是返回了一個空數(shù)組,說明不對所依附的Compoent的任何事件產(chǎn)生關(guān)聯(lián)。 但是在實際使用時,往往通過重載這個方法來告訴Yii,這個行為將對Compoent的何種事件,使用哪個方法進(jìn)行處理。
比如,Yii自帶的 yii\behaviors\AttributeBehavior 類,定義了在一個 ActiveRecord 對象的某些事件發(fā)生時, 自動對某些字段進(jìn)行修改的行為。 他有一個很常用的子類 yii\behaviors\TimeStampBehavior 用于將指定的字段設(shè)置為一個當(dāng)前的時間戳。 常用于表示最后修改日期、上次登陸時間等場景。我們以這個行為為例,來分析行為響應(yīng)事件的原理。
在 yii\behaviors\AttributeBehavior::event()
中,代碼如下:
public function events()
{
return array_fill_keys(array_keys($this->attributes),
'evaluateAttributes');
}
這段代碼的意思這里不作過多深入,學(xué)有余力的讀者朋友可以自行研究,難度并不高。 這里,你只需要大致知道,這段代碼將返回一個數(shù)組,其鍵值為 $this->attributes
數(shù)組的鍵值, 數(shù)組元素的值為成員函數(shù)evaluateAttributes 。
而在 yii\behaviors\TimeStampBehavior::init()
中,有以下的代碼:
public function init()
{
parent::init();
if (empty($this->attributes)) {
// 重點看這里
$this->attributes = [
BaseActiveRecord::EVENT_BEFORE_INSERT =>
[$this->createdAtAttribute, $this->updatedAtAttribute],
BaseActiveRecord::EVENT_BEFORE_UPDATE =>
$this->updatedAtAttribute,
];
}
}
上面的代碼重點看的是對于 $this->attributes
的初始化部分。 結(jié)合上面2個方法的代碼,對于yii\base\Behavior::events()
的返回數(shù)組,其格式應(yīng)該是這樣的:
return [
BaseActiveRecord::EVENT_BEFORE_INSERT => 'evaluateAttributes',
BaseActiveRecord::EVENT_BEFORE_UPDATE => 'evaluateAttributes',
];
數(shù)組的鍵值用于指定要響應(yīng)的事件, 這里是 BaseActiveRecord::EVENT_BEFORE_INSERT
和BaseActiveRecord::EVENT_BEFORE_UPDATE
。 數(shù)組的值是一個事件handler,如上面的 evaluateAttributes 。
那么一旦TimeStampBehavior與某個ActiveRecord綁定,就會調(diào)用 yii\behaviors\TimeStampBehavior::attach()
, 那么就會有:
// 這里 $owner 是某個 ActiveRecord
public function attach($owner)
{
$this->owner = $owner;
// 遍歷上面提到的 events() 所定義的數(shù)組
foreach ($this->events() as $event => $handler) {
// 調(diào)用 ActiveRecord::on 來綁定事件
// 這里 $handler 為字符串 `evaluateAttributes`
// 因此,相當(dāng)于調(diào)用 on(BaseActiveRecord::EVENT_BEFORE_INSERT,
// [$this, 'evaluateAttributes'])
$owner->on($event, is_string($handler) ? [$this, $handler] :
$handler);
}
}
因此,事件 BaseActiveRecord::EVENT_BEFORE_INSERT
和 BaseActiveRecord::EVENT_BEFORE_UPDATE
就綁定到了ActiveRecord上了。當(dāng)新建記錄或更新記錄時, TimeStampBehavior::evaluateAttributes
就會被觸發(fā)。 從而實現(xiàn)時間戳的功能。具體可以看看 yii\behaviors\AttributeBehavior::evaluateAttributes()
和yii\behaviors\TimeStampBehavior::getValues()
的代碼。這里因為只是具體功能實現(xiàn),對于行為的理解關(guān)系不大。 就不把代碼粘出來占用篇幅了。
行為的屬性和方法注入原理
上面我們了解到了行為的用意在于將自身的屬性和方法注入給所依附的類。 那么Yii中是如何將一個行為yii\base\Behavior 的屬性和方法, 注入到一個 yii\base\Component 中的呢? 對于屬性而言,是通過 __get()
和__set()
魔術(shù)方法來實現(xiàn)的。 對于方法,是通過 __call()
方法。
屬性的注入
以讀取為例,如果訪問 $Component->property1
,Yii在幕后干了些什么呢? 這個看看 yii\base\Component::__get()
public function __get($name)
{
$getter = 'get' . $name;
if (method_exists($this, $getter)) {
return $this->$getter();
} else {
// 注意這個 else 分支的內(nèi)容,正是與 yii\base\Object::__get() 的
// 不同之處
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canGetProperty($name)) {
// 屬性在行為中須為 public。否則不可能通過下面的形式訪問呀。
return $behavior->$name;
}
}
}
if (method_exists($this, 'set' . $name)) {
throw new InvalidCallException('Getting write-only property: ' .
get_class($this) . '::' . $name);
} else {
throw new UnknownPropertyException('Getting unknown property: ' .
get_class($this) . '::' . $name);
}
}
重點來看 yii\base\Compoent::__get()
與 yii\base\Object::__get()
的不同之處。 就是在于對于未定義getter函數(shù)之后的處理, yii\base\Object 是直接拋出異常, 告訴你想要訪問的屬性不存在之類。 但是 yii\base\Component則是在不存在getter之后,還要看看是不是注入的行為的屬性:
- 首先,調(diào)用了
$this->ensureBehaviors()
。這個方法已經(jīng)在前面講過了,主要是確保行為已經(jīng)綁定。
- 在確保行為已經(jīng)綁定后,開始遍歷
$this->_behaviors
。 Yii將類所有綁定的行為都保存在yii\base\Compoent::$_behaviors[]
數(shù)組中。
- 最后,通過行為的
canGetProperty()
判斷這個屬性, 是否是所綁定行為的可讀屬性,如果是,就返回這個行為的這個屬性 $behavior->name
。 完成屬性的讀取。 至于 canGetProperty()
已經(jīng)在 :ref::property
部分已經(jīng)簡單講過了, 后面還會有針對性地一個介紹。
對于setter,代碼類似,這里就不占用篇幅了。
方法的注入
與屬性的注入通過 __get()
__set()
魔術(shù)方法類似, Yii通過 __call()
魔術(shù)方法實現(xiàn)對行為中方法的注入:
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()");
}
從上面的代碼中可以看出,Yii還是先是調(diào)用了 $this->ensureBehaviors()
確保行為已經(jīng)綁定。
然后,也是遍歷 yii\base\Component::$_behaviros[]
數(shù)組。 通過 hasMethod()
方法判斷方法是否存在。 如果所綁定的行為中要調(diào)用的方法存在,則使用PHP的 call_user_func_array()
調(diào)用之。 至于 hasMethod() 方法,我們后面再講。
注入屬性與方法的訪問控制
在前面我們針對行為中public和private、protected的成員在所綁定的類中是否可訪問舉出了具體例子。 這里我們從代碼層面解析原因。
在上面的內(nèi)容,我們知道,一個屬性可不可訪問,主要看行為的 canGetProperty()
和 canSetProperty()
。 而一個方法可不可調(diào)用,主要看行為的 hasMethod() 。 由于 yii\base\Behavior 繼承自我們的老朋友 yii\base\Object ,所以上面提到的三個判斷方法, 事實上代碼都在 Object 中。我們一個一個來看:
public function canGetProperty($name, $checkVars = true)
{
return method_exists($this, 'get' . $name) || $checkVars
property_exists($this, $name);
}
public function canSetProperty($name, $checkVars = true)
{
return method_exists($this, 'set' . $name) || $checkVars
property_exists($this, $name);
}
public function hasMethod($name)
{
return method_exists($this, $name);
}
這三個方法真的談不上復(fù)雜。對此,我們可以得出以下結(jié)論:
- 當(dāng)向Component綁定的行為讀?。▽懭耄┮粋€屬性時,如果行為為該屬性定義了一個getter (setter),則可以訪問。 或者,如果行為確實具有該成員變量即可通過上面的判斷,此時,該成員變量可為 public, private, protected。 但最終只有 public 的成員變量才能正確訪問。原因在上面講注入的原理時已經(jīng)交待了。
- 當(dāng)調(diào)用Component綁定的行為的一個方法時,如果行為已經(jīng)定義了該方法,即可通過上面的判斷。 此時,這個方法可以為 public, private, protected。 但最終只有 public 的方法才能正確調(diào)用。如果你理解了上一款的原因,那么這里也就理解了。
行為與繼承和特性(Traits) 的區(qū)別
從實現(xiàn)的效果看,你是不是會認(rèn)為Yii真是多此一舉?PHP中要達(dá)到這樣的效果,可以使用繼承呀,可以使用PHP新引入的特性(Traits)呀。但是,行為具有繼承和特性所沒有的優(yōu)點,從實際使用的角度講,繼承和特性更靠底層點??康讓樱鸵馕吨_發(fā)效率低,運(yùn)行效率高。行為的引入,是以可以接受的運(yùn)行效率犧牲為成本,謀取開發(fā)效率大提升的一筆買賣。
行為與繼承
首先來講,拿行為與繼承比較,從邏輯上是不對的,這兩者是在完全不同的層面上的事物,是不對等的。之所以進(jìn)行比較,是因為在實現(xiàn)的效果上,兩者有的類似的地方。看起來,行為和繼承都可以使一個類具有另一個類的屬性和方法,從而達(dá)到擴(kuò)充類的功能的目的。
相比較于使用繼承的方式來擴(kuò)充類功能,使用行為的方式,一是不必對現(xiàn)有類進(jìn)行修改,二是PHP不支持多繼承,但是Yii可以綁定多個行為,從而達(dá)到類似多繼承的效果。
反過來,行為是絕對無法替代繼承的。亞洲人,美洲人都是地球人,你可以將亞洲人和美洲人當(dāng)成地球人來對待。但是,你絕對不能把一只在某些方面表現(xiàn)得像人的猴子,真的當(dāng)成人來對待。
這里就不展開講了。從本質(zhì)上來講,行為只是一種設(shè)計模式,是解決問題的方法學(xué)。繼承則是PHP作為編程語言所提供的特性,根本不在一個層次上。
行為與特性
特性是PHP5.4之后引入的一個新feature。從實現(xiàn)效果看,行為與特性都達(dá)到把自身的public 變量、屬性、方法注入到當(dāng)前類中去的目的。在使用上,他們也各有所長,但總的原則可以按下面的提示進(jìn)行把握。
傾向于使用行為的情況:
- 行為從本質(zhì)上講,也是PHP的類,因此一個行為可以繼承自另一個行為,從而實現(xiàn)代碼的復(fù)用。而特性只是PHP的一種語法,效果上類似于把特性的代碼導(dǎo)入到了類中從而實現(xiàn)代碼的注入,特性是不支持繼承的。
- 行為可以動態(tài)地綁定、解除,而不必要對類進(jìn)行修改。但是特性必須在類在使用 use 語句,要解除特性時,則要刪除這個語句。換句話說,需要對類進(jìn)行修改。
- 行為還以在在配置階段進(jìn)行綁定,特性就不行了。
- 行為可以用于對事件進(jìn)行反饋,而特性不行。
- 當(dāng)出現(xiàn)命名沖突時,行為會自行排除沖突,自動使用先綁定的行為。而特性在發(fā)生沖突時,需要人為干預(yù),修改發(fā)生沖突的變量名、屬性名、方法名。
傾向于使用特性的情況:
- 特性比行為在效率上要高一點,因為行為其實是類的實例,需要時間和空間進(jìn)行分配。
- 特性是PHP的語法,因此,IDE的支持要好一些。目前還沒有IDE能支持行為。
更多關(guān)于Yii相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《Yii框架入門及常用技巧總結(jié)》、《php優(yōu)秀開發(fā)框架總結(jié)》、《smarty模板入門基礎(chǔ)教程》、《php面向?qū)ο蟪绦蛟O(shè)計入門教程》、《php字符串(string)用法總結(jié)》、《php+mysql數(shù)據(jù)庫操作入門教程》及《php常見數(shù)據(jù)庫操作技巧匯總》
希望本文所述對大家基于Yii框架的PHP程序設(shè)計有所幫助。
您可能感興趣的文章:- Yii框架組件和事件行為管理詳解
- Yii框架組件的事件機(jī)制原理與用法分析
- Yii2表單事件之Ajax提交實現(xiàn)方法
- 深入解析PHP的Yii框架中的event事件機(jī)制
- 詳解在PHP的Yii框架中使用行為Behaviors的方法
- 詳解PHP的Yii框架中組件行為的屬性注入和方法注入
- yii2學(xué)習(xí)教程之5種內(nèi)置行為類詳解
- PHP的Yii框架中行為的定義與綁定方法講解
- yii2行為的方法如何注入到組件類中詳解
- PHP的Yii框架中移除組件所綁定的行為的方法
- YII框架行為behaviors用法示例
- YII2框架中behavior行為的理解與使用方法示例
- Yii Framework框架中事件和行為的區(qū)別及應(yīng)用實例分析