溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

YII2.0框架行為Behavior的示例分析

發(fā)布時(shí)間:2021-08-18 11:24:23 來(lái)源:億速云 閱讀:116 作者:小新 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要為大家展示了“YII2.0框架行為Behavior的示例分析”,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“YII2.0框架行為Behavior的示例分析”這篇文章吧。

行為(Behavior)

使用行為(behavior)可以在不修改現(xiàn)有類(lèi)的情況下,對(duì)類(lèi)的功能進(jìn)行擴(kuò)充。 通過(guò)將行為綁定到一個(gè)類(lèi),可以使類(lèi)具有行為本身所定義的屬性和方法,就好像類(lèi)本來(lái)就有這些屬性和方法一樣。 而且不需要寫(xiě)一個(gè)新的類(lèi)去繼承或包含現(xiàn)有類(lèi)。

Yii中的行為,其實(shí)是 yii\base\Behavior 類(lèi)的實(shí)例, 只要將一個(gè)Behavior實(shí)例綁定到任意的 yii\base\Component 實(shí)例上, 這個(gè)Component就可以擁有該Behavior所定義的屬性和方法了。而如果將行為與事件關(guān)聯(lián)起來(lái),可以玩的花樣就更多了。

但有一點(diǎn)需要注意,Behavior只能與Component類(lèi)綁定。 他們是天生的一對(duì),愛(ài)情不是你想買(mǎi),想買(mǎi)就能買(mǎi)的,必要的物質(zhì)是少不了的,奮斗吧少年。 所以,如果你寫(xiě)了一個(gè)類(lèi),需要使用到行為,那么就果斷地繼承自yii\base\Component 。

同時(shí),行為單獨(dú)靠Behavior一方是實(shí)現(xiàn)不了的,就好像愛(ài)情不是一廂情愿。 為了支持Behavior,Yii對(duì)于yii\base\Component 也進(jìn)行了精心設(shè)計(jì),這兩者共同配合,才有了神奇的行為。

使用行為

一個(gè)綁定了行為的類(lèi),表現(xiàn)起來(lái)是這樣的:

// Step 1: 定義一個(gè)將綁定行為的類(lèi)
class MyClass extends yii\base\Component
{
 // 空的
}
// Step 2: 定義一個(gè)行為類(lèi),他將綁定到MyClass上
class MyBehavior extends yii\base\Behavior
{
 // 行為的一個(gè)屬性
 public $property1 = 'This is property in MyBehavior.';
 // 行為的一個(gè)方法
 public function method1()
 {
  return 'Method in MyBehavior is called.';
 }
}
$myClass = new MyClass();
$myBehavior = new MyBehavior();
// Step 3: 將行為綁定到類(lèi)上
$myClass->attachBehavior('myBehavior', $myBehavior);
// Step 4: 訪問(wèn)行為中的屬性和方法,就和訪問(wèn)類(lèi)自身的屬性和方法一樣
echo $myClass->property1;
echo $myClass->method1();

上面的代碼你不用全都看懂,雖然你可能已經(jīng)用腳趾頭猜到了這些代碼的意思, 但這里你只需要記住行為中的屬性和方法可以被所綁定的類(lèi)像訪問(wèn)自身的屬性和方法一樣直接訪問(wèn)就OK了。 代碼中, $myClass 是沒(méi)有property1 method() 成員的。這倆是 $myBehavior 的成員。 但是,通過(guò) attachBehavior() 將行為綁定到對(duì)象之后, $myCalss 就好像練成了吸星大法、化功大法,表現(xiàn)的財(cái)大氣粗,將別人的屬性和方法都變成了自己的。

另外,從上面的代碼中,你還要掌握使用行為的大致流程:

  • 從 yii\base\Component 派生自己的類(lèi),以便使用行為;

  • 從 yii\base\Behavior 派生自己的行為類(lèi),里面定義行為涉及到的屬性、方法;

  • 將Component和Behavior綁定起來(lái);

  • 像使用Component自身的屬性和方法一樣,盡情使用行為中定義的屬性和方法。

行為的要素

我們提到了行為只是 yii\base\Behavior 類(lèi)的實(shí)例。 那么這個(gè)類(lèi)究竟有什么秘密呢?其實(shí)說(shuō)破了也沒(méi)有什么的他只是一個(gè)簡(jiǎn)單的封裝而已,非常的簡(jiǎn)單:

class Behavior extends Object
{
 // 指向行為本身所綁定的Component對(duì)象
 public $owner;
 // Behavior 基類(lèi)本身沒(méi)用,主要是子類(lèi)使用,重載這個(gè)函數(shù)返回一個(gè)數(shù)組表
 // 示行為所關(guān)聯(lián)的事件
 public function events()
 {
  return [];
 }
 // 綁定行為到 $owner
 public function attach($owner)
 {
  ... ...
 }
 // 解除綁定
 public function detach()
 {
  ... ...
 }
}

這就是Behavior的全部代碼了,是不是很簡(jiǎn)單?Behavior類(lèi)的要素的確很簡(jiǎn)單:

  • $owner 成員變量,用于指向行為的依附對(duì)象;

  • events() 用于表示行為所有要響應(yīng)的事件;

  • attach() 用于將行為與Component綁定起來(lái);

  • deatch() 用于將行為從Component上解除。

下面分別進(jìn)行講解。

行為的依附對(duì)象

yii\base\Behavior::$owner 指向的是Behavior實(shí)例本身所依附的對(duì)象。這是行為中引用所依附對(duì)象的唯一手段了。 通過(guò)這個(gè) $owner ,行為才能訪問(wèn)所依附的Component,才能將本身的方法作為事件handler綁定到Component上。

$owner 由 yii\base\Behavior::attach() 進(jìn)行賦值。 也就是在將行為綁定到某個(gè)Component時(shí), $owner 就已經(jīng)名花有主了。 一般情況下,不需要你自己手動(dòng)去指定 $owner 的值, 在調(diào)用 yii\base\Componet::attachBehavior() 將行為與對(duì)象綁定時(shí), Component會(huì)自動(dòng)地將 $this 作為參數(shù),調(diào)用 yii\base\Behavior::attach() 。

有一點(diǎn)需要格外注意,由于行為從本質(zhì)來(lái)講是一個(gè)PHP類(lèi),其方法就是類(lèi)方法,就是成員函數(shù)。 所以,在行為的方法中, $this 引用的是行為本身, 試圖通過(guò) $this 來(lái)訪問(wèn)行為所依附的Component是行不通的。 正確的方法是通過(guò) yii\base\Behavior::$owner 來(lái)訪問(wèn)Component。

行為所要響應(yīng)的事件

行為與事件結(jié)合后,可以在不對(duì)類(lèi)作修改的情況下,補(bǔ)充類(lèi)在事件觸發(fā)后的各種不同反應(yīng)。 為此,只需要重載yii\base\Behavior::events() 方法,表示這個(gè)行為將對(duì)類(lèi)的何種事件進(jìn)行何種反饋即可:

namespace app\Components;
use yii\db\ActiveRecord;
use yii\base\Behavior;
class MyBehavior extends Behavior
{
 // 重載events() 使得在事件觸發(fā)時(shí),調(diào)用行為中的一些方法
 public function events()
 {
  // 在EVENT_BEFORE_VALIDATE事件觸發(fā)時(shí),調(diào)用成員函數(shù) beforeValidate
  return [
   ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
  ];
 }
 // 注意beforeValidate 是行為的成員函數(shù),而不是綁定的類(lèi)的成員函數(shù)。
 // 還要注意,這個(gè)函數(shù)的簽名,要滿足事件handler的要求。
 public function beforeValidate($event)
 {
  // ...
 }
}

上面的代碼中, events() 返回一個(gè)數(shù)組,表示所要做出響應(yīng)的事件, 上例中的事件是ActiveRecord::EVENT_BEFORE_VALIDATE ,以數(shù)組的鍵來(lái)表示, 而數(shù)組的值則表示做好反應(yīng)的事件handler,上例中是beforeValidate() ,事件handler可以是以下形式:

  • 字符串,表示行為類(lèi)的方法,如上面的例就是這種情況。 這個(gè)是與事件handler不同的,事件handler中使用字符串時(shí),是表示PHP全局函數(shù),而這里表示行為類(lèi)內(nèi)部的方法。

  • 一個(gè)對(duì)象或類(lèi)的成員函數(shù),以數(shù)組的形式,如 [$object, 'methodName'] 。這個(gè)與事件handler是一致的。

  • 一個(gè)匿名函數(shù)。

對(duì)于事件響應(yīng)函數(shù)的簽名,要求與事件handler一樣:

function ($event) { }

具體內(nèi)容,請(qǐng)參考 事件(Event) 的內(nèi)容。

行為的綁定與解除

說(shuō)到綁定與解除,這意味著這個(gè)事情有2方,行為和Component。單獨(dú)一方是沒(méi)有綁定或解除的說(shuō)法的。 因此,這里我們先賣(mài)一關(guān)子,等后面講綁定和解除的原理時(shí),再來(lái)講有關(guān)的內(nèi)容。

這里你只需要知道,對(duì)于綁定和解除,Behavior 分別使用 attach()detach() 來(lái)實(shí)現(xiàn)就OK了。

定義一個(gè)行為

定義一個(gè)行為,就是準(zhǔn)備好要注入到現(xiàn)有類(lèi)中去的屬性和方法, 這些屬性和方法要寫(xiě)到一個(gè) yii\base\Behavior 類(lèi)中。 所以,定義一個(gè)行為,就是寫(xiě)一個(gè) Behavior的子類(lèi),子類(lèi)中包含了所要注入的屬性和方法:

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()
 {
  // ...
 }
}

上面的代碼通過(guò)定義一個(gè) app\Components\MyBehavior 類(lèi)而定義一個(gè)行為。 由于 MyBehavior 繼承自yii\base\Behavior 從而間接地繼承自 yii\base\Object 。 沒(méi)錯(cuò),這是我們的老朋友了。因此,這個(gè)類(lèi)有一個(gè)public的成員變量 prop1 , 一個(gè)只讀屬性 prop2 ,一個(gè)只寫(xiě)屬性 prop3 ,一個(gè)public的方法 foo() 。 另外,還有一個(gè)private 的成員變量 $_prop4 ,一個(gè)protected 的方法 bar() 。 如果你不清楚只讀屬性和只寫(xiě)屬性,最好回頭看看 屬性(Property) 部分的內(nèi)容。

當(dāng)這MyBehavior與一個(gè)Component綁定后, 綁定的Component也就擁有了 prop1 prop2 這兩個(gè)屬性和方法foo() ,因?yàn)樗麄兌际?public 的。 而 private 的 $_prop4 和 protected 的 bar 就得不到了。 至于原因么,后面講行為注入的原理時(shí),我們?cè)俳忉尅?/p>

行為的綁定

行為的綁定通常是由Component來(lái)發(fā)起。有兩種方式可以將一個(gè)Behavior綁定到一個(gè) yii\base\Component 。 一種是靜態(tài)的方法,另一種是動(dòng)態(tài)的。靜態(tài)的方法在實(shí)踐中用得比較多一些。 因?yàn)橐话闱闆r下,在你的代碼沒(méi)跑起來(lái)之前,一個(gè)類(lèi)應(yīng)當(dāng)具有何種行為,是確定的。 動(dòng)態(tài)綁定的方法主要是提供了更靈活的方式,但實(shí)際使用中并不多見(jiàn)。

靜態(tài)方法綁定行為

靜態(tài)綁定行為,只需要重載 yii\base\Component::behaviors() 就可以了。 這個(gè)方法用于描述類(lèi)所具有的行為。如何描述呢? 使用配置來(lái)描述,可以是Behavior類(lèi)名,也可以是Behavior類(lèi)的配置數(shù)組:

namespace app\models;
use yii\db\ActiveRecord;
use app\Components\MyBehavior;
class User extends ActiveRecord
{
 public function behaviors()
 {
  return [
   // 匿名的行為,僅直接給出行為的類(lèi)名稱(chēng)
   MyBehavior::className(),
   // 名為myBehavior2的行為,也是僅給出行為的類(lèi)名稱(chēng)
   'myBehavior2' => MyBehavior::className(),
   // 匿名行為,給出了MyBehavior類(lèi)的配置數(shù)組
   [
    'class' => MyBehavior::className(),
    'prop1' => 'value1',
    'prop3' => 'value3',
   ],
   // 名為myBehavior4的行為,也是給出了MyBehavior類(lèi)的配置數(shù)組
   'myBehavior4' => [
    'class' => MyBehavior::className(),
    'prop1' => 'value1',
    'prop3' => 'value3',
   ]
  ];
 }
}

還有一個(gè)靜態(tài)的綁定辦法,就是通過(guò)配置文件來(lái)綁定:

[
 'as myBehavior2' => MyBehavior::className(),
 'as myBehavior3' => [
  'class' => MyBehavior::className(),
  'prop1' => 'value1',
  'prop3' => 'value3',
 ],
]

具體參見(jiàn)配置項(xiàng)(Configuration) 部分的內(nèi)容。

動(dòng)態(tài)方法綁定行為

動(dòng)態(tài)綁定行為,需要調(diào)用 yii\base\Compoent::attachBehaviors():

$Component->attachBehaviors([
 'myBehavior1' => new MyBehavior, // 這是一個(gè)命名行為
 MyBehavior::className(),   // 這是一個(gè)匿名行為
]);

這個(gè)方法接受一個(gè)數(shù)組參數(shù),參數(shù)的含義與上面靜態(tài)綁定行為是一樣一樣的。

在上面的這些例子中,以數(shù)組的鍵作為行為的命名,而對(duì)于沒(méi)有提供鍵名的行為,就是匿名行為。

對(duì)于命名的行為,可以調(diào)用 yii\base\Component::getBehavior() 來(lái)取得這個(gè)綁定好的行為:

$behavior = $Component->getBehavior('myBehavior2');

對(duì)于匿名的行為,則沒(méi)有辦法直接引用了。但是,可以獲取所有的綁定好的行為:

$behaviors = $Component->getBehaviors();

綁定的內(nèi)部原理

只是重載一個(gè) yii\base\Component::behaviors() 就可以這么神奇地使用行為了? 這只是冰山的一角,實(shí)際上關(guān)系到綁定的過(guò)程,有關(guān)的方面有:

  • yii\base\Component::behaviors()

  • yii\base\Component::ensureBehaviors()

  • yii\base\Component::attachBehaviorInternal()

  • yii\base\Behavior::attach()

4個(gè)方法中,Behavior只占其一,更多的代碼,是在Component中完成的。

yii\base\Component::behaviors() 上面講靜態(tài)方法綁定行為時(shí)已經(jīng)提到了,就是返回一個(gè)數(shù)組用于描述行為。 那么yii\base\Component::ensuerBehaviors() 呢?

這個(gè)方法會(huì)在Component的諸多地方調(diào)用 __get() __set() __isset() __unset() __call() canGetProperty()hasMethod() hasEventHandlers() on() off() 等用到,看到這么多是不是頭疼?一點(diǎn)都不復(fù)雜,一句話,只要涉及到類(lèi)的屬性、方法、事件這個(gè)函數(shù)都會(huì)被調(diào)用到。

這么眾星拱月,被諸多凡人所需要的 ensureBehaviors() 究竟是何許人也? 就像名字所表明的,他的作用在于“ensure” 。其實(shí)只是確保 behaviors() 中所描述的行為已經(jīng)進(jìn)行了綁定而已:

public function ensureBehaviors()
{
 // 為null表示尚未綁定
 // 多說(shuō)一句,為空數(shù)組表示沒(méi)有綁定任何行為
 if ($this->_behaviors === null) {
  $this->_behaviors = [];
  // 遍歷 $this->behaviors() 返回的數(shù)組,并綁定
  foreach ($this->behaviors() as $name => $behavior) {
   $this->attachBehaviorInternal($name, $behavior);
  }
 }
}

這個(gè)方法主要是對(duì)子類(lèi)用的, yii\base\Compoent 沒(méi)有任何預(yù)先注入的行為,所以,這個(gè)調(diào)用沒(méi)有用。 但是對(duì)于子類(lèi),你可能重載了 yii\base\Compoent::behaviros() 來(lái)預(yù)先注入一些行為。 那么,這個(gè)函數(shù)會(huì)將這些行為先注入進(jìn)來(lái)。

從上面的代碼中,自然就看到了接下來(lái)要說(shuō)的第三個(gè)東東, yii\base\Component\attachBehaviorInternal():

private function attachBehaviorInternal($name, $behavior)
{
 // 不是 Behavior 實(shí)例,說(shuō)是只是類(lèi)名、配置數(shù)組,那么就創(chuàng)建出來(lái)吧
 if (!($behavior instanceof Behavior)) {
  $behavior = Yii::createObject($behavior);
 }
 // 匿名行為
 if (is_int($name)) {
  $behavior->attach($this);
  $this->_behaviors[] = $behavior;
 // 命名行為
 } else {
  // 已經(jīng)有一個(gè)同名的行為,要先解除,再將新的行為綁定上去。
  if (isset($this->_behaviors[$name])) {
   $this->_behaviors[$name]->detach();
  }
  $behavior->attach($this);
  $this->_behaviors[$name] = $behavior;
 }
 return $behavior;
}

首先要注意到,這是一個(gè)private成員。其實(shí)在Yii中,所有后綴為 *Internal 的方法,都是私有的。 這個(gè)方法干了這么幾件事:

  • 如果 $behavior 參數(shù)并非是一個(gè) Behavior 實(shí)例,就以之為參數(shù),用 Yii::createObject() 創(chuàng)建出來(lái)。

  • 如果以匿名行為的形式綁定行為,那么直接將行為附加在這個(gè)類(lèi)上。

  • 如果是命名行為,先看看是否有同名的行為已經(jīng)綁定在這個(gè)類(lèi)上,如果有,用后來(lái)的行為取代之前的行為。

yii\base\Component::attachBehaviorInternal() 中, 以 $this 為參數(shù)調(diào)用了 yii\base\Behavior::attach() 。 從而,引出了跟綁定相關(guān)的最后一個(gè)家伙 yii\base\Behavior::attach() , 這也是前面我們講行為的要素時(shí)沒(méi)講完的。先看看代碼:

public function attach($owner)
{
 $this->owner = $owner;
 foreach ($this->events() as $event => $handler) {
  $owner->on($event, is_string($handler) ? [$this, $handler] :
   $handler);
 }
}

上面的代碼干了兩件事:

  • 設(shè)置好行為的 $owner ,使得行為可以訪問(wèn)、操作所依附的對(duì)象

  • 遍歷行為中的 events() 返回的數(shù)組,將準(zhǔn)備響應(yīng)的事件,通過(guò)所依附類(lèi)的 on() 綁定到類(lèi)上

說(shuō)了這么多,關(guān)于綁定,做個(gè)小結(jié):

  • 綁定的動(dòng)作從Component發(fā)起;

  • 靜態(tài)綁定通過(guò)重載 yii\base\Componet::behaviors() 實(shí)現(xiàn);

  • 動(dòng)態(tài)綁定通過(guò)調(diào)用 yii\base\Component::attachBehaviors() 實(shí)現(xiàn);

  • 行為還可以通過(guò)為 Component 配置 as 配置項(xiàng)進(jìn)行綁定;

  • 行為有匿名行為和命名行為之分,區(qū)別在于綁定時(shí)是否給出命名。 命名行為可以通過(guò)其命名進(jìn)行標(biāo)識(shí),從而有針對(duì)性地進(jìn)行解除等操作;

  • 綁定過(guò)程中,后綁定的行為會(huì)取代已經(jīng)綁定的同名行為;

  • 綁定的意義有兩點(diǎn),一是為行為設(shè)置 $owner 。二是將行為中擬響應(yīng)的事件的handler綁定到類(lèi)中去。

解除行為

解除行為只需調(diào)用 yii\base\Component::detachBehavior() 就OK了:

$Component->detachBehavior('myBehavior2');

這樣就可以解除已經(jīng)綁定好的名為 myBehavior2 的行為了。 但是,對(duì)于匿名行為,這個(gè)方法就無(wú)從下手了。不過(guò)我們可以一不做二不休,解除所有綁定好的行為:

$Component->detachBehaviors();

這上面兩種方法,都會(huì)調(diào)用到 yii\base\Behavior::detach() ,其代碼如下:

public function detach()
{
 // 這得是個(gè)名花有主的行為才有解除一說(shuō)
 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() 相反,解除的過(guò)程就是干兩件事: 一是將 $owner 設(shè)置為 null ,表示這個(gè)行為沒(méi)有依附到任何類(lèi)上。 二是通過(guò)Component的 off() 將綁定到類(lèi)上的事件hanlder解除下來(lái)。一句話,善始善終。

行為響應(yīng)的事件實(shí)例

上面的綁定和解除過(guò)程,我們看到Y(jié)ii費(fèi)了那么大勁,主要就是為了將行為中的事件handler綁定到類(lèi)中去。 在實(shí)際編程時(shí),行為用得最多的,也是對(duì)于Compoent各種事件的響應(yīng)。 通過(guò)行為注入,可以在不修改現(xiàn)有類(lèi)的代碼的情況下,更改、擴(kuò)展類(lèi)對(duì)于事件的響應(yīng)和支持。 使用這個(gè)技巧,可以玩出很炫的花樣。 而要將行為與Component的事件關(guān)聯(lián)起來(lái),就要通過(guò) yii\base\Behavior::events() 方法。

上面Behavior基類(lèi)的代碼中,這個(gè)方法只是返回了一個(gè)空數(shù)組,說(shuō)明不對(duì)所依附的Compoent的任何事件產(chǎn)生關(guān)聯(lián)。 但是在實(shí)際使用時(shí),往往通過(guò)重載這個(gè)方法來(lái)告訴Yii,這個(gè)行為將對(duì)Compoent的何種事件,使用哪個(gè)方法進(jìn)行處理。

比如,Yii自帶的 yii\behaviors\AttributeBehavior 類(lèi),定義了在一個(gè) ActiveRecord 對(duì)象的某些事件發(fā)生時(shí), 自動(dòng)對(duì)某些字段進(jìn)行修改的行為。 他有一個(gè)很常用的子類(lèi) yii\behaviors\TimeStampBehavior 用于將指定的字段設(shè)置為一個(gè)當(dāng)前的時(shí)間戳。 常用于表示最后修改日期、上次登陸時(shí)間等場(chǎng)景。我們以這個(gè)行為為例,來(lái)分析行為響應(yīng)事件的原理。

yii\behaviors\AttributeBehavior::event() 中,代碼如下:

public function events()
{
 return array_fill_keys(array_keys($this->attributes),
  'evaluateAttributes');
}

這段代碼的意思這里不作過(guò)多深入,學(xué)有余力的讀者朋友可以自行研究,難度并不高。 這里,你只需要大致知道,這段代碼將返回一個(gè)數(shù)組,其鍵值為 $this->attributes 數(shù)組的鍵值, 數(shù)組元素的值為成員函數(shù)evaluateAttributes 。

而在 yii\behaviors\TimeStampBehavior::init() 中,有以下的代碼:

public function init()
{
 parent::init();
 if (empty($this->attributes)) {
  // 重點(diǎn)看這里
  $this->attributes = [
   BaseActiveRecord::EVENT_BEFORE_INSERT =>
    [$this->createdAtAttribute, $this->updatedAtAttribute],
   BaseActiveRecord::EVENT_BEFORE_UPDATE =>
    $this->updatedAtAttribute,
  ];
 }
}

上面的代碼重點(diǎn)看的是對(duì)于 $this->attributes 的初始化部分。 結(jié)合上面2個(gè)方法的代碼,對(duì)于yii\base\Behavior::events() 的返回?cái)?shù)組,其格式應(yīng)該是這樣的:

return [
 BaseActiveRecord::EVENT_BEFORE_INSERT => 'evaluateAttributes',
 BaseActiveRecord::EVENT_BEFORE_UPDATE => 'evaluateAttributes',
];

數(shù)組的鍵值用于指定要響應(yīng)的事件, 這里是 BaseActiveRecord::EVENT_BEFORE_INSERTBaseActiveRecord::EVENT_BEFORE_UPDATE 。 數(shù)組的值是一個(gè)事件handler,如上面的 evaluateAttributes 。

那么一旦TimeStampBehavior與某個(gè)ActiveRecord綁定,就會(huì)調(diào)用 yii\behaviors\TimeStampBehavior::attach() , 那么就會(huì)有:

// 這里 $owner 是某個(gè) ActiveRecord
public function attach($owner)
{
 $this->owner = $owner;
 // 遍歷上面提到的 events() 所定義的數(shù)組
 foreach ($this->events() as $event => $handler) {
  // 調(diào)用 ActiveRecord::on 來(lái)綁定事件
  // 這里 $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_INSERTBaseActiveRecord::EVENT_BEFORE_UPDATE 就綁定到了ActiveRecord上了。當(dāng)新建記錄或更新記錄時(shí), TimeStampBehavior::evaluateAttributes 就會(huì)被觸發(fā)。 從而實(shí)現(xiàn)時(shí)間戳的功能。具體可以看看 yii\behaviors\AttributeBehavior::evaluateAttributes()yii\behaviors\TimeStampBehavior::getValues() 的代碼。這里因?yàn)橹皇蔷唧w功能實(shí)現(xiàn),對(duì)于行為的理解關(guān)系不大。 就不把代碼粘出來(lái)占用篇幅了。

行為的屬性和方法注入原理

上面我們了解到了行為的用意在于將自身的屬性和方法注入給所依附的類(lèi)。 那么Yii中是如何將一個(gè)行為yii\base\Behavior 的屬性和方法, 注入到一個(gè) yii\base\Component 中的呢? 對(duì)于屬性而言,是通過(guò) __get()__set() 魔術(shù)方法來(lái)實(shí)現(xiàn)的。 對(duì)于方法,是通過(guò) __call() 方法。

屬性的注入

以讀取為例,如果訪問(wèn) $Component->property1 ,Yii在幕后干了些什么呢? 這個(gè)看看 yii\base\Component::__get()

public function __get($name)
{
 $getter = 'get' . $name;
 if (method_exists($this, $getter)) {
  return $this->$getter();
 } else {
  // 注意這個(gè) else 分支的內(nèi)容,正是與 yii\base\Object::__get() 的
  // 不同之處
  $this->ensureBehaviors();
  foreach ($this->_behaviors as $behavior) {
   if ($behavior->canGetProperty($name)) {
    // 屬性在行為中須為 public。否則不可能通過(guò)下面的形式訪問(wèn)呀。
    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);
 }
}

重點(diǎn)來(lái)看 yii\base\Compoent::__get()yii\base\Object::__get() 的不同之處。 就是在于對(duì)于未定義getter函數(shù)之后的處理, yii\base\Object 是直接拋出異常, 告訴你想要訪問(wèn)的屬性不存在之類(lèi)。 但是 yii\base\Component則是在不存在getter之后,還要看看是不是注入的行為的屬性:

  • 首先,調(diào)用了 $this->ensureBehaviors() 。這個(gè)方法已經(jīng)在前面講過(guò)了,主要是確保行為已經(jīng)綁定。

  • 在確保行為已經(jīng)綁定后,開(kāi)始遍歷 $this->_behaviors 。 Yii將類(lèi)所有綁定的行為都保存在yii\base\Compoent::$_behaviors[] 數(shù)組中。

  • 最后,通過(guò)行為的 canGetProperty() 判斷這個(gè)屬性, 是否是所綁定行為的可讀屬性,如果是,就返回這個(gè)行為的這個(gè)屬性 $behavior->name 。 完成屬性的讀取。 至于 canGetProperty() 已經(jīng)在 :ref::property 部分已經(jīng)簡(jiǎn)單講過(guò)了, 后面還會(huì)有針對(duì)性地一個(gè)介紹。

對(duì)于setter,代碼類(lèi)似,這里就不占用篇幅了。

方法的注入

與屬性的注入通過(guò) __get() __set() 魔術(shù)方法類(lèi)似, Yii通過(guò) __call() 魔術(shù)方法實(shí)現(xiàn)對(duì)行為中方法的注入:

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ù)組。 通過(guò) hasMethod() 方法判斷方法是否存在。 如果所綁定的行為中要調(diào)用的方法存在,則使用PHP的 call_user_func_array() 調(diào)用之。 至于 hasMethod() 方法,我們后面再講。

注入屬性與方法的訪問(wèn)控制

在前面我們針對(duì)行為中public和private、protected的成員在所綁定的類(lèi)中是否可訪問(wèn)舉出了具體例子。 這里我們從代碼層面解析原因。

在上面的內(nèi)容,我們知道,一個(gè)屬性可不可訪問(wèn),主要看行為的 canGetProperty()canSetProperty() 。 而一個(gè)方法可不可調(diào)用,主要看行為的 hasMethod() 。 由于 yii\base\Behavior 繼承自我們的老朋友 yii\base\Object ,所以上面提到的三個(gè)判斷方法, 事實(shí)上代碼都在 Object 中。我們一個(gè)一個(gè)來(lái)看:

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);
}

這三個(gè)方法真的談不上復(fù)雜。對(duì)此,我們可以得出以下結(jié)論:

  • 當(dāng)向Component綁定的行為讀取(寫(xiě)入)一個(gè)屬性時(shí),如果行為為該屬性定義了一個(gè)getter (setter),則可以訪問(wèn)。 或者,如果行為確實(shí)具有該成員變量即可通過(guò)上面的判斷,此時(shí),該成員變量可為 public, private, protected。 但最終只有 public 的成員變量才能正確訪問(wèn)。原因在上面講注入的原理時(shí)已經(jīng)交待了。

  • 當(dāng)調(diào)用Component綁定的行為的一個(gè)方法時(shí),如果行為已經(jīng)定義了該方法,即可通過(guò)上面的判斷。 此時(shí),這個(gè)方法可以為 public, private, protected。 但最終只有 public 的方法才能正確調(diào)用。如果你理解了上一款的原因,那么這里也就理解了。

行為與繼承和特性(Traits) 的區(qū)別

從實(shí)現(xiàn)的效果看,你是不是會(huì)認(rèn)為Yii真是多此一舉?PHP中要達(dá)到這樣的效果,可以使用繼承呀,可以使用PHP新引入的特性(Traits)呀。但是,行為具有繼承和特性所沒(méi)有的優(yōu)點(diǎn),從實(shí)際使用的角度講,繼承和特性更靠底層點(diǎn)??康讓樱鸵馕吨_(kāi)發(fā)效率低,運(yùn)行效率高。行為的引入,是以可以接受的運(yùn)行效率犧牲為成本,謀取開(kāi)發(fā)效率大提升的一筆買(mǎi)賣(mài)。

行為與繼承

首先來(lái)講,拿行為與繼承比較,從邏輯上是不對(duì)的,這兩者是在完全不同的層面上的事物,是不對(duì)等的。之所以進(jìn)行比較,是因?yàn)樵趯?shí)現(xiàn)的效果上,兩者有的類(lèi)似的地方。看起來(lái),行為和繼承都可以使一個(gè)類(lèi)具有另一個(gè)類(lèi)的屬性和方法,從而達(dá)到擴(kuò)充類(lèi)的功能的目的。

相比較于使用繼承的方式來(lái)擴(kuò)充類(lèi)功能,使用行為的方式,一是不必對(duì)現(xiàn)有類(lèi)進(jìn)行修改,二是PHP不支持多繼承,但是Yii可以綁定多個(gè)行為,從而達(dá)到類(lèi)似多繼承的效果。

反過(guò)來(lái),行為是絕對(duì)無(wú)法替代繼承的。亞洲人,美洲人都是地球人,你可以將亞洲人和美洲人當(dāng)成地球人來(lái)對(duì)待。但是,你絕對(duì)不能把一只在某些方面表現(xiàn)得像人的猴子,真的當(dāng)成人來(lái)對(duì)待。

這里就不展開(kāi)講了。從本質(zhì)上來(lái)講,行為只是一種設(shè)計(jì)模式,是解決問(wèn)題的方法學(xué)。繼承則是PHP作為編程語(yǔ)言所提供的特性,根本不在一個(gè)層次上。

行為與特性

特性是PHP5.4之后引入的一個(gè)新feature。從實(shí)現(xiàn)效果看,行為與特性都達(dá)到把自身的public 變量、屬性、方法注入到當(dāng)前類(lèi)中去的目的。在使用上,他們也各有所長(zhǎng),但總的原則可以按下面的提示進(jìn)行把握。

傾向于使用行為的情況:

  • 行為從本質(zhì)上講,也是PHP的類(lèi),因此一個(gè)行為可以繼承自另一個(gè)行為,從而實(shí)現(xiàn)代碼的復(fù)用。而特性只是PHP的一種語(yǔ)法,效果上類(lèi)似于把特性的代碼導(dǎo)入到了類(lèi)中從而實(shí)現(xiàn)代碼的注入,特性是不支持繼承的。

  • 行為可以動(dòng)態(tài)地綁定、解除,而不必要對(duì)類(lèi)進(jìn)行修改。但是特性必須在類(lèi)在使用 use 語(yǔ)句,要解除特性時(shí),則要?jiǎng)h除這個(gè)語(yǔ)句。換句話說(shuō),需要對(duì)類(lèi)進(jìn)行修改。

  • 行為還以在在配置階段進(jìn)行綁定,特性就不行了。

  • 行為可以用于對(duì)事件進(jìn)行反饋,而特性不行。

  • 當(dāng)出現(xiàn)命名沖突時(shí),行為會(huì)自行排除沖突,自動(dòng)使用先綁定的行為。而特性在發(fā)生沖突時(shí),需要人為干預(yù),修改發(fā)生沖突的變量名、屬性名、方法名。

傾向于使用特性的情況:

  • 特性比行為在效率上要高一點(diǎn),因?yàn)樾袨槠鋵?shí)是類(lèi)的實(shí)例,需要時(shí)間和空間進(jìn)行分配。

  • 特性是PHP的語(yǔ)法,因此,IDE的支持要好一些。目前還沒(méi)有IDE能支持行為。

以上是“YII2.0框架行為Behavior的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI