行为
行为
行为是 yii\base\Behavior
或其子类的实例。行为,也称为 mixins,可以无须改变类继承关系即可增强一个已有的 yii\base\Component
组件类功能。当行为附加到组件后,它将“注入”它的方法和属性到组件,然后可以像访问组件内定义的方法和属性一样访问它们。此外,行为通过组件能响应被触发的 事件,从而自定义或调整组件正常执行的代码。
定义行为
要定义行为,通过继承 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()
方法。
Tip: 在行为内部可以通过 [[yii\base\Behavior::owner]] 属性访问行为已附加的组件。
Note: 如果 [[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)
{
// 处理器方法逻辑
}
}
[[yii\base\Behavior::events()]] 方法返回事件列表和相应的处理器。上例声明了 [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE]] 事件和它的处理器 beforeValidate()
。当指定一个事件处理器时,要使用以下格式之一:
- 指向行为类的方法名的字符串,如上例所示;
- 对象或类名和方法名的数组,如
[$object, 'methodName']
; - 匿名方法。
处理器的格式如下,其中 $event
指向事件参数。关于事件的更多细节请参考 事件:
function ($event) {
}
附加行为
可以静态或动态地附加行为到 yii\base\Component
组件。前者在实践中更常见。
要静态附加行为,覆写行为要附加的组件类的 [[yii\base\Component::behaviors()]] 方法即可。[[yii\base\Component::behaviors()]] 方法应该返回行为 配置 列表。每个行为配置可以是行为类名也可以是配置数组。如:
namespace app\models;
use yii\db\ActiveRecord;
use app\components\MyBehavior;
class User extends ActiveRecord
{
public function behaviors()
{
return [
// 匿名行为,只有行为类名
MyBehavior::class,
// 命名行为,只有行为类名
'myBehavior2' => MyBehavior::class,
// 匿名行为,配置数组
[
'class' => MyBehavior::class,
'prop1' => 'value1',
'prop2' => 'value2',
],
// 命名行为,配置数组
'myBehavior4' => [
'class' => MyBehavior::class,
'prop1' => 'value1',
'prop2' => 'value2',
]
];
}
}
通过指定行为配置数组相应的键可以给行为关联一个名称。这种行为称为 命名行为。上例中,有两个命名行为:myBehavior2
和 myBehavior4
。如果行为没有指定名称就是 匿名行为。
要动态附加行为,在对应组件里调用 [[yii\base\Component::attachBehavior()]] 方法即可,如:
use app\components\MyBehavior;
// 附加行为对象
$component->attachBehavior('myBehavior1', new MyBehavior);
// 附加行为类
$component->attachBehavior('myBehavior2', MyBehavior::class);
// 附加配置数组
$component->attachBehavior('myBehavior3', [
'class' => MyBehavior::class,
'prop1' => 'value1',
'prop2' => 'value2',
]);
可以通过 [[yii\base\Component::attachBehaviors()]] 方法一次附加多个行为:
$component->attachBehaviors([
'myBehavior1' => new MyBehavior, // 命名行为
MyBehavior::class, // 匿名行为
]);
还可以通过 配置 去附加行为:
[
'as myBehavior2' => MyBehavior::class,
'as myBehavior3' => [
'class' => MyBehavior::class,
'prop1' => 'value1',
'prop2' => 'value2',
],
]
详情请参考 配置 章节。
使用行为
使用行为,必须像前文描述的一样先把它附加到 yii\base\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();
内置行为
Yii 有几种内置行为可用:
属性行为
yii\behaviors\AttributeBehavior
这个行为,在发生特定事件时自动为 ActiveRecord 对象的一个或多个属性指定一个指定的值。
要使用该行为,请配置 attributes
属性,该属性应指定需要更新的属性列表,以及应该触发更新的相应事件。然后,用一个 PHP 可调用对象来配置 value
属性,该对象的返回值将被用来分配给当前属性。例如:
use yii\behaviors\AttributeBehavior;
public function behaviors()
{
return [
[
'class' => AttributeBehavior::class,
// 可以只定义 insert 或 update 之一,缺省的那个表示不执行写表
'attributes' => [
// insert 表的时候
ActiveRecord::EVENT_BEFORE_INSERT => 'attribute1',
// update 表的时候
ActiveRecord::EVENT_BEFORE_UPDATE => 'attribute2',
],
'value' => function ($event) {
return 'some value';
},
],
];
}
由于属性值将由该行为自动设置,它们通常不是用户输入,因此不应该被验证,也就是说,它们不应该出现在模型的 [[yii\base\Model::rules()] 方法中。
时间戳行为
yii\behaviors\TimestampBehavior
这个行为,支持在 yii\db\ActiveRecord
活动记录存储时,自动更新它的时间戳属性,通常是创建时间和更新时间,它是 yii\behaviors\AttributeBehavior
行为的子类,要使它,请在你的 ActiveRecord 类中插入以下代码:
use yii\behaviors\TimestampBehavior;
public function behaviors()
{
return [
TimestampBehavior::class,
];
}
再完整一些,如下:
namespace app\models\User;
use yii\db\ActiveRecord;
use yii\behaviors\TimestampBehavior;
class User extends ActiveRecord
{
// ...
public function behaviors()
{
return [
[
'class' => TimestampBehavior::class,
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
],
// 如果你想用 datetime 代替 UNIX 时间戳:
// 'value' => new Expression('NOW()'),
],
];
}
}
默认情况下,当关联的 AR 对象被 insert 时,该行为将用当前时间戳填充 created_at
和 updated_at
属性,当 AR 对象被 update 时,它将用时间戳填充 updated_at
属性。时间戳值由 time()
获取。
由于属性值将由该行为自动设置,它们通常不是用户输入,因此不应该被验证,也就是说,created_at
和 updated_at
不应该出现在模型的 [[yii\base\Model::rules()] 方法中。
对于以上使用 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; // 显示当前时间戳
如果你的属性名不同,或想使用其他方式来计算时间戳,你可以配置 createdAtAttribute
、updatedAtAttribute
和 value
属性,如下所示:
use yii\db\Expression;
public function behaviors()
{
return [
[
'class' => TimestampBehavior::class,
'createdAtAttribute' => 'create_time',
'updatedAtAttribute' => 'update_time',
'value' => new Expression('NOW()'),
],
];
}
如果你像上面的例子一样,使用一个 yii\db\Expression
对象,该属性将不保存时间戳值,而是在记录保存之后的 Expression 对象本身。之后,如果你需要 DB 的值,你应该调用记录的 \yii\db\ActiveRecord::refresh()
方法。
该行为还提供了一个名为 yii\behaviors\TimestampBehavior::touch()
的方法,允许您将当前的时间戳分配给指定的属性,并将它们保存到数据库中。例如:
$model->touch('creation_time');
追责行为
yii\behaviors\BlameableBehavior
这个行为,自动用当前用户 ID 填充指定的属性,通常是创建人 ID 和 更新人 ID。它也是 yii\behaviors\AttributeBehavior
行为的子类,要使用它,可以在 ActiveRecord 类中插入以下代码:
use yii\behaviors\BlameableBehavior;
public function behaviors()
{
return [
BlameableBehavior::class,
];
}
默认情况下,当关联的 AR 对象被 insert 时,该行为将用当前用户 ID 填充 created_by
和 updated_by
属性,当 AR 对象被 update 时,它将用当前用户 ID 填充 updated_by
属性。
由于属性值将由该行为自动设置,它们通常不是用户输入,因此不应该被验证,也就是说,created_by
和 updated_by
不应该出现在模型的 [[yii\base\Model::rules ()] 方法中。
如果你的属性名不同,你可以配置 createdByAttribute
和 updatedByAttribute
属性,如下所示:
public function behaviors()
{
return [
[
'class' => BlameableBehavior::class,
'createdByAttribute' => 'author_id',
'updatedByAttribute' => 'updater_id',
],
];
}
SEO URL Slug 行为
yii\behaviors\SluggableBehavior
这个行为,会自动用一个值填充指定的属性,这个值可以在 URL 中作为一个 slug 使用。它也是 yii\behaviors\AttributeBehavior
行为的子类。
Slug,可以参考 yoast.com/slug/#slug ,一般是指网页 URL 链接使用 slug 技术,来做 SEO。它是 URL 的一部分,它以一种易于阅读的形式,标识网站上的特定页面。
Note: 此行为依赖 php-intl 扩展进行音译。如果没有安装,则返回
yii\helpers\Inflector::$transliteration
中定义的来替代。
要使用该行为,插入以下代码到你的 ActiveRecord 类:
use yii\behaviors\SluggableBehavior;
public function behaviors()
{
return [
[
'class' => SluggableBehavior::class,
'attribute' => 'title',
// 'slugAttribute' => 'slug',
],
];
}
默认情况下,当相关的 AR 对象被验证时,该行为将可以在 URL 中使用一个 slug 值来填充 slug
属性。
由于属性值会通过该行为自动设置,它们通常不是用户输入的,因此不应该被验证,也就是说,slug
属性不应该出现在模型的 [[yii\base\Model::rules()] 方法中。
如果你的属性名不同,可以像下面这样配置 slugAttribute
属性:
public function behaviors()
{
return [
[
'class' => SluggableBehavior::class,
'slugAttribute' => 'alias',
],
];
}
属性类型转换行为
从 2.0.10 版本开始,yii\behaviors\AttributeTypecastBehavior
这个行为,提供自动模型属性类型转换的能力。
该行为是非常有用的,尤其是在针对无模式数据库,如 MongoDB 、Redis等,且使用 ActiveRecord 的情况下。对于常规的 yii\db\ActiveRecord
,甚至 yii\base\Model
,它也可以派上用场,允许在模型验证后维护严格的属性类型。
该行为应该被附加到 yii\base\Model
或 yii\db\BaseActiveRecord
的子类。您应该通过 attributeTypes
指定确切的属性类型。例如:
use yii\behaviors\AttributeTypecastBehavior;
class Item extends \yii\db\ActiveRecord
{
public function behaviors()
{
return [
'typecast' => [
'class' => AttributeTypecastBehavior::class,
'attributeTypes' => [
'amount' => AttributeTypecastBehavior::TYPE_INTEGER,
'price' => AttributeTypecastBehavior::TYPE_FLOAT,
'is_active' => AttributeTypecastBehavior::TYPE_BOOLEAN,
],
'typecastAfterValidate' => true,
'typecastBeforeSave' => false,
'typecastAfterFind' => false,
],
];
}
// ...
}
Tip: 当
attributeTypes
的值将根据所有者验证规则自动检测的情况下,您可以将它置空。下面的例子将自动创建与上面配置相同的attributeTypes
值:
use yii\behaviors\AttributeTypecastBehavior;
class Item extends \yii\db\ActiveRecord
{
public function rules()
{
return [
['amount', 'integer'],
['price', 'number'],
['is_active', 'boolean'],
];
}
public function behaviors()
{
return [
'typecast' => [
'class' => AttributeTypecastBehavior::class,
// 'attributeTypes' will be composed automatically according to `rules()`
],
];
}
// ...
}
该行为允许在以下情况下,自动进行属性类型转换:
- 模型验证成功之后
- 模型保存(insert 或 update)之前
- 模型查询(通过查询找到或刷新)之后
你可以使用字段 typecastAfterValidate
,typecastbeforeave
和 typecastAfterFind
来控制特定情况下的自动类型转换。默认情况下,只有在模型验证之后才会执行类型转换。
Note:你可以在调用
yii\behaviors\AttributeTypecastBehavior::typecastAttributes()
方法时手动触发属性类型转换,示例如下:
$model = new Item();
$model->price = '38.5';
$model->is_active = 1;
$model->typecastAttributes();
批量属性行为
从 2.0.13 版本开始,yii\behaviors\AttributesBehavior
这个行为,在当某些事件触发时,自动赋值给ActiveRecord 对象的一个或多个属性。
To use AttributesBehavior, configure the [[attributes]] property which should specify the list of attributes that need to be updated and the corresponding events that should trigger the update.
要使用该行为,请配置 attributes
属性,该属性应指定需要更新的属性列表,以及应该触发更新的相应事件。然后,使用一个 PHP 回调来配置封装数组的值,该返回值将被用于分配给当前属性。例如:
use yii\behaviors\AttributesBehavior;
public function behaviors()
{
return [
[
'class' => AttributesBehavior::class,
'attributes' => [
'attribute1' => [
ActiveRecord::EVENT_BEFORE_INSERT => new Expression('NOW()'),
ActiveRecord::EVENT_BEFORE_UPDATE => \Yii::$app->formatter->asDatetime('2017-07-13'),
],
'attribute2' => [
ActiveRecord::EVENT_BEFORE_VALIDATE => [$this, 'storeAttributes'],
ActiveRecord::EVENT_AFTER_VALIDATE => [$this, 'restoreAttributes'],
],
'attribute3' => [
ActiveRecord::EVENT_BEFORE_VALIDATE => $fn2 = [$this, 'getAttribute2'],
ActiveRecord::EVENT_AFTER_VALIDATE => $fn2,
],
'attribute4' => [
ActiveRecord::EVENT_BEFORE_DELETE => function ($event, $attribute) {
static::disabled() || $event->isValid = false;
},
],
],
],
];
}
由于属性值将由该行为自动设置,它们通常不是用户输入,因此不应该被验证,也就是说,它们不应该出现在模型的 [[yii\base\Model::rules()] 方法中。
可缓存小部件行为
从 2.0.14 版本开始,yii\behaviors\CacheableWidgetBehavior
这个行为,可以根据指定的持续时间和依赖项,自动缓存小部件内容。
如果应用程序配置了 cache
缓存组件,该行为可以在没有任何配置的情况下使用。默认情况下,小部件将被缓存 60 秒。
下面的例子将无限期地缓存 posts 小部件,直到 post 被修改:
use yii\behaviors\CacheableWidgetBehavior;
public function behaviors()
{
return [
[
'class' => CacheableWidgetBehavior::class,
'cacheDuration' => 0,
'cacheDependency' => [
'class' => 'yii\caching\DbDependency',
'sql' => 'SELECT MAX(updated_at) FROM posts',
],
],
];
}
乐观锁行为
从 2.0.16 版本开始,yii\behaviors\OptimisticLockBehavior
这个行为,使用 [[yii\db\BaseActiveRecord::optimisticLock()]] 返回的列名,并自动升级一个模型的锁版本,它也是 yii\behaviors\AttributeBehavior 行为的子类。
乐观锁定允许多个用户访问同一条记录进行编辑,从而避免了潜在的冲突。当用户试图保存一些过期数据的记录时(因为另一个用户修改了数据),一个 yii\db\StaleObjectException
异常将被抛出,并且跳过更新或删除操作。
要使用该行为,首先按照 [[yii\db\BaseActiveRecord::optimisticLock()] 中列出的步骤启用乐观锁,再从你的 ActiveRecord 类的 [[yii\base\Model::rules()] 方法中移除持有锁版本的列名,然后向其添加以下代码:
use yii\behaviors\OptimisticLockBehavior;
public function behaviors()
{
return [
OptimisticLockBehavior::class,
];
}
默认情况下,该行为将使用 [[yii\web\Request::getBodyParam()]] 解析提交的值,或者在任何失败时将其设置为 0,这意味着不包含 version 属性的请求可能实现对实体的首次成功更新,但是从这里开始,任何进一步的尝试都应该失败,除非请求持有预期的版本号。
一旦附加行为,如果版本号不是由 [[yii\web\Request::getBodyParam()] 保存,模型类的内部使用也会失败。扩展您的模型类可能是有用的,通过重写 [[yii\db\BaseActiveRecord::optimisticLock()]] 在父类中启用乐观锁,然后将该行为附加到子类,以至于您可以将父模型绑定到内部使用,同时将持有此行为的子模型连接到负责接收最终用户输入的控制器。
或者,您也可以使用 PHP 回调 配置 value
属性来实现不同的逻辑。
该行为还提供了一个名为 upgrade()
的方法,该方法将模型的版本增加 1,当你需要在连接的客户端之间将一个实体标记为陈旧的,并避免任何更改,直到他们再次加载它时,这可能是有用的:
$model->upgrade();
其它行为
有几种外部行为可用:
- yii2tech\ar\softdelete\SoftDeleteBehavior - 提供软删除和软恢复 ActiveRecord 的 方法。即将记录标记为已删除的设置标记或状态。
- yii2tech\ar\position\PositionBehavior - 允许通过提供重新排序方法来管理整数字段中的记录顺序。
比较行为与 Trait
虽然行为类似于 Trait,它们都将自己的属性和方法“注入”到主类中,但它们在许多方面有所不同。如下所述,他们都有优点和缺点。它们更像互补类而非替代类。
使用行为的原因
行为类像普通类支持继承。另一方面,Trait 可以视为 PHP 语言支持的复制粘贴功能,它不支持继承。
行为无须修改组件类就可动态附加到组件或移除。要使用 Trait,必须修改使用它的类。
行为是可配置的,而 Trait 则不可行。
行为可以通过响应事件来定制组件的代码执行。
当附属于同一组件的不同行为之间可能存在名称冲突时,通过优先考虑附加到该组件的行为,自动解决冲突。由不同 Trait 引起的名称冲突需要通过重命名受影响的属性或方法进行手动解决。
使用 Trait 的原因
Trait 比行为更有效,因为行为是既需要时间又需要内存的对象。
因为 IDE 是一种本地语言结构,所以它们对 Trait 更友好。
💖喜欢本文档的,欢迎点赞、收藏、留言或转发,谢谢支持!
作者邮箱:zhuzixian520@126.com,github地址:github.com/zhuzixian520