行爲
行爲
行爲是 [[yii\base\Behavior]] 或其子類的實例。行爲,也稱爲 mixins,可以無須改變類繼承關係即可增強一個已有的 [[yii\base\Component|組件]] 類功能。當行爲附加到組件後,它將「注入」它的方法和屬性到組件,然後可以像訪問組件內定義的方法和屬性一樣訪問它們。此外,行爲通過組件能響應被觸發的事件,從而自定義或調整組件正常執行的代碼。
定義行爲
要定義行爲,通過繼承 [[yii\base\Behavior]] 或其子類來建立一個類。如:
- namespace app\\components; 
- use yii\\base\\Model; 
- 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\Behavior::events()]] 方法,如:
- namespace app\\components; 
- use yii\\db\\ActiveRecord; 
- use yii\\base\\Behavior; 
- class MyBehavior extends Behavior 
- { 
- // 其它代碼 
- public function events() 
- { 
- return [ 
- ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate', 
- ]; 
- } 
- public function beforeValidate($event) 
- { 
- // 處理器方法邏輯 
- } 
- } 
[[yii\base\Behavior::events()|events()]] 方法返回事件列表和相應的處理器。上例聲明瞭 [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] 事件和它的處理器beforeValidate() 。當指定一個事件處理器時,要使用以下格式之一:
- 指向行爲類的方法名的字符串,如上例所示;
- 對象或類名和方法名的數組,如 [$object, 'methodName'];
- 匿名方法。
處理器的格式如下,其中 $event 指向事件參數。關於事件的更多細節請參考事件:
- function ($event) {
- }
附加行爲
可以靜態或動態地附加行爲到[[yii\base\Component|組件]]。前者在實踐中更常見。
要靜態附加行爲,覆寫行爲要附加的組件類的 [[yii\base\Component::behaviors()|behaviors()]] 方法即可。[[yii\base\Component::behaviors()|behaviors()]] 方法應該返回行爲配置列表。每個行爲配置可以是行爲類名也可以是配置數組。如:
- namespace app\\models; 
- use yii\\db\\ActiveRecord; 
- use app\\components\\MyBehavior; 
- class User extends ActiveRecord 
- { 
- public function behaviors() 
- { 
- return [ 
- // 匿名行爲,只有行爲類名 
- MyBehavior::className(), 
- // 命名行爲,只有行爲類名 
- 'myBehavior2' => MyBehavior::className(), 
- // 匿名行爲,配置數組 
- [ 
- 'class' => MyBehavior::className(), 
- 'prop1' => 'value1', 
- 'prop2' => 'value2', 
- ], 
- // 命名行爲,配置數組 
- 'myBehavior4' => [ 
- 'class' => MyBehavior::className(), 
- 'prop1' => 'value1', 
- 'prop2' => 'value2', 
- ] 
- ]; 
- } 
- } 
通過指定行爲配置數組相應的鍵可以給行爲關聯一個名稱。這種行爲稱爲命名行爲。上例中,有兩個命名行爲:myBehavior2 和 myBehavior4 。如果行爲沒有指定名稱就是匿名行爲。
要動態附加行爲,在對應組件裏調用 [[yii\base\Component::attachBehavior()]] 方法即可,如:
- use app\\components\\MyBehavior; 
- // 附加行爲對象 
- $component->attachBehavior('myBehavior1', new MyBehavior); 
- // 附加行爲類 
- $component->attachBehavior('myBehavior2', MyBehavior::className()); 
- // 附加配置數組 
- $component->attachBehavior('myBehavior3', [ 
- 'class' => MyBehavior::className(), 
- 'prop1' => 'value1', 
- 'prop2' => 'value2', 
- ]); 
可以通過 [[yii\base\Component::attachBehaviors()]] 方法一次附加多個行爲:
- $component->attachBehaviors([
- 'myBehavior1' => new MyBehavior, // 命名行爲
- MyBehavior::className(), // 匿名行爲
- ]);
還可以通過配置去附加行爲:
- [ 
- 'as myBehavior2' => MyBehavior::className(), 
- 'as myBehavior3' => [ 
- 'class' => MyBehavior::className(), 
- 'prop1' => 'value1', 
- 'prop2' => 'value2', 
- ], 
- ] 
詳情請參考配置章節。
使用行爲
使用行爲,必須像前文描述的一樣先把它附加到 [[yii\base\Component|component]] 類或其子類。一旦行爲附加到組件,就可以直接使用它。
行爲附加到組件後,可以通過組件訪問一個行爲的公共成員變量或 getter 和 setter 方法定義的屬性:
- // "prop1" 是定義在行爲類的屬性
- echo $component->prop1;
- $component->prop1 = $value;
類似地也可以調用行爲的\公共*方法:
- // bar() 是定義在行爲類的公共方法
- $component->bar();
如你所見,儘管 $component 未定義 prop1 和 bar() ,它們用起來也像組件自己定義的一樣。
如果兩個行爲都定義了一樣的屬性或方法,並且它們都附加到同一個組件,那麼首先附加上的行爲在屬性或方法被訪問時有優先權。
附加行爲到組件時的命名行爲,可以使用這個名稱來訪問行爲對象,如下所示:
- $behavior = $component->getBehavior('myBehavior');
也能獲取附加到這個組件的所有行爲:
- $behaviors = $component->getBehaviors();
移除行爲
要移除行爲,可以調用 [[yii\base\Component::detachBehavior()]] 方法用行爲相關聯的名字實現:
- $component->detachBehavior('myBehavior1');
也可以移除全部行爲:
- $component->detachBehaviors();
使用 TimestampBehavior
最後以 [[yii\behaviors\TimestampBehavior]] 的講解來結尾,這個行爲支持在 [[yii\db\ActiveRecord|Active Record]] 存儲時自動更新它的時間戳屬性。
首先,附加這個行爲到計劃使用該行爲的 [[yii\db\ActiveRecord|Active Record]] 類:
- namespace app\\models\\User; 
- use yii\\db\\ActiveRecord; 
- use yii\\behaviors\\TimestampBehavior; 
- class User extends ActiveRecord 
- { 
- // ... 
- public function behaviors() 
- { 
- return [ 
- [ 
- 'class' => TimestampBehavior::className(), 
- 'attributes' => [ 
- ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'], 
- ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'], 
- ], 
- ], 
- ]; 
- } 
- } 
以上指定的行爲數組:
- 當記錄插入時,行爲將當前時間戳賦值給 created_at 和 updated_at 屬性;
- 當記錄更新時,行爲將當前時間戳賦值給 updated_at 屬性。
保存 User 對象,將會發現它的 created_at 和 updated_at 屬性自動填充了當前時間戳:
``php $user = new User; $user->email = '[email protected]'; $user->save(); echo $user->created_at; // 顯示當前時間戳
[[yii\behaviors\TimestampBehavior|TimestampBehavior]] 行爲還提供了一個有用的方法 [[yii\behaviors\TimestampBehavior::touch()|touch()]],這個方法能將當前時間戳賦值給指定屬性並保存到數據庫:
```php
$user->touch('login_time');與 PHP traits 的比較
儘管行爲在 "注入" 屬性和方法到主類方面類似於 traits ,它們在很多方面卻不相同。如上所述,它們各有利弊。它們更像是互補的而不是相互替代。
行爲的優勢
行爲類像普通類支持繼承。另一方面,traits 可以視爲 PHP 語言支持的複製粘貼功能,它不支持繼承。
行爲無須修改組件類就可動態附加到組件或移除。要使用 traits,必須修改使用它的類。
行爲是可配置的而 traits 不能。
行爲以響應事件來自定義組件的代碼執行。
當不同行爲附加到同一組件產生命名衝突時,這個衝突通過先附加行爲的優先權自動解決。而由不同 traits 引發的命名衝突需要通過手工重命名衝突屬性或方法來解決。
traits 的優勢
traits 比起行爲更高效,因爲行爲是對象,消耗時間和內存。
IDE 對 traits 更友好,因爲它們是語言結構。