行为(Behaviors)
行为
行为是 yii\base\Behavior 或其子类的实例。 行为,也称为 mixins, 可以无须改变类继承关系即可增强一个已有的 组件 类功能。 当行为附加到组件后,它将“注入”它的方法和属性到组件, 然后可以像访问组件内定义的方法和属性一样访问它们。 此外,行为通过组件能响应被触发的事件,从而自定义或调整组件正常执行的代码。
定义行为
要定义行为,通过继承 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 方法定义属性
因为这是一个行为类,当它附加到一个组件时,该组件也将具有 prop1
和 prop2
属性和 foo()
方法。
提示: 在行为内部可以通过 yii\base\Behavior::$owner 属性访问行为已附加的组件。
注意: 如果 yii\base\Behavior::__get() 和/或 yii\base\Behavior::__set() 行为方法被覆盖, 则需要覆盖 yii\base\Behavior::canGetProperty() 和/或 yii\base\Behavior::canSetProperty()。
处理事件
如果要让行为响应对应组件的事件触发, 就应覆写 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)
{
// 处理器方法逻辑
}
}
events() 方法返回事件列表和相应的处理器。 上例声明了 EVENT_BEFORE_VALIDATE 事件和它的处理器 beforeValidate()
。 当指定一个事件处理器时,要使用以下格式之一:
- 指向行为类的方法名的字符串,如上例所示;
- 对象或类名和方法名的数组,如
[$object, 'methodName']
; - 匿名方法。
处理器的格式如下,其中 $event
指向事件参数。 关于事件的更多细节请参考事件:
function ($event) {
}
附加行为
可以静态或动态地附加行为到组件。前者在实践中更常见。
要静态附加行为,覆写行为要附加的组件类的 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',
],
]
详情请参考 配置章节。
使用行为
使用行为,必须像前文描述的一样先把它附加到 component 类或其子类。一旦行为附加到组件,就可以直接使用它。
行为附加到组件后,可以通过组件访问一个行为的公共成员变量 或 getter 和 setter 方法定义的属性:
// "prop1" 是定义在行为类的属性
echo $component->prop1;
$component->prop1 = $value;
类似地也可以调用行为的公共方法:
// foo() 是定义在行为类的公共方法
$component->foo();
如你所见,尽管 $component
未定义 prop1
和 foo()
, 它们用起来也像组件自己定义的一样。
如果两个行为都定义了一样的属性或方法,并且它们都附加到同一个组件, 那么首先附加上的行为在属性或方法被访问时有优先权。
附加行为到组件时的命名行为,可以使用这个名称来访问行为对象, 如下所示:
$behavior = $component->getBehavior('myBehavior');
也能获取附加到这个组件的所有行为:
$behaviors = $component->getBehaviors();
移除行为
要移除行为,可以调用 yii\base\Component::detachBehavior() 方法用行为相关联的名字实现:
$component->detachBehavior('myBehavior1');
也可以移除全部行为:
$component->detachBehaviors();
使用 TimestampBehavior
最后以 yii\behaviors\TimestampBehavior 的讲解来结尾, 这个行为支持在 Active Record 存储时自动更新它的时间戳属性。
首先,附加这个行为到计划使用该行为的 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'],
],
// if you're using datetime instead of UNIX timestamp:
// 'value' => new Expression('NOW()'),
],
];
}
}
以上指定的行为数组:
- 当记录插入时,行为将当前时间戳赋值给
created_at
和updated_at
属性; - 当记录更新时,行为将当前时间戳赋值给
updated_at
属性。
注意: 对于上述实现使用MySQL数据库,请将列 (
created_at
,updated_at
) 定义为 int(11) 作为 UNIX 时间戳。
有了以上这段代码,如果你有一个 User
对象并且试图保存它,你会发现它的 created_at
和 updated_at
被当前的UNIX时间戳自动填充:
$user = new User;
$user->email = 'test@example.com';
$user->save();
echo $user->created_at; // 显示当前时间戳
TimestampBehavior 行为还提供了一个有用的方法 touch(), 这个方法能将当前时间戳赋值给指定属性并保存到数据库:
$user->touch('login_time');
其它行为
有几种内置和外部行为可用:
- yii\behaviors\BlameableBehavior – 使用当前用户 ID 自动填充指定的属性。
- yii\behaviors\SluggableBehavior – 自动填充指定的属性,其值可以在 URL 中用作 slug。
- yii\behaviors\AttributeBehavior – 在发生特定事件时自动为 ActiveRecord 对象的一个或多个属性 指定一个指定的值。
- yii2tech\ar\softdelete\SoftDeleteBehavior – 提供软删除和软恢复 ActiveRecord 的 方法。即将记录标记为已删除的设置标记或状态。
- yii2tech\ar\position\PositionBehavior – 允许通过提供重新排序方法来 管理整数字段中的记录顺序。
比较行为与 Traits
虽然行为类似于 traits,它们都将自己的属性和方法“注入”到主类中, 但它们在许多方面有所不同。如下所述,他们都有优点和缺点。 它们更像互补类而非替代类。
使用行为的原因
行为类像普通类支持继承。另一方面,traits 可以视为 PHP 语言支持的复制粘贴功能, 它不支持继承。
行为无须修改组件类就可动态附加到组件或移除。 要使用 traits,必须修改使用它的类。
行为是可配置的,而 traits 则不可行。
行为可以通过响应事件来定制组件的代码执行。
当附属于同一组件的不同行为之间可能存在名称冲突时, 通过优先考虑附加到该组件的行为, 自动解决冲突。由不同 traits 引起的名称冲突需要通过 重命名受影响的属性或方法进行手动解决。
使用 Traits 的原因 ¶
Traits 比行为更有效,因为行为是既需要时间又需要内存的对象。
因为 IDE 是一种本地语言结构,所以它们对 Traits 更友好。