应用场景: 在我们的Model开发中, 当你添加或者修改一条数据时, 你会发现很多都会涉及到创建时间和修改时间. 例如: 我们插入数据要添加"创建时间, 修改时间", 更新数据时要更新"修改时间". 那么多数据表的操作都重复涉及, 你会不会发现太麻烦了? 下面我就介绍一种时间戳行为, 他可以自动对某些字段进行修改的行为
下面是我的一个model类中, 使用到了行为behaviors, 代码如下: 一种: 当你的表中的创建时间和修改时间命名是created_at,updated_at时, 我们重写下面的behaviors()来实现上述行为
use yii\behaviors\TimestampBehavior;
/**
* 创建时间/修改时间
*/
public function behaviors()
{
return [
TimestampBehavior::className(), // 匿名的行为,仅直接给出行为的类名称
];
}
第二种: 当你的表中的创建时间和修改时间命名不是created_at,updated_at时,你可以使用下面这种方式进行重写behaviors()来实现上述行为
use yii\db\Expression;
public function behaviors()
{
return [
[
'class' => TimestampBehavior::className(),
'createdAtAttribute' => 'create_time', //此时以create_time/update_at为例,实际情况可根自己表中字段名为主
'updatedAtAttribute' => 'update_time',
],
];
}
就上面的一个方法,我们就是实现了: 关于添加一条数据中的"创建时间, 更新时间" 和 更新一条数据中的"修改时间", 只需在对应的model里面使用此方法, 那么你在添加或者修改数据时, 都不用再管创建时间和修改时间的事儿了, 他会自动进行添加创建时间和修改时间了.
下面我们来说说原因:
使用行为(behavior)可以在不修改现有类的情况下,对类的功能进行扩充。 通过将行为绑定到一个类,可以使类具有行为本身所定义的属性和方法,就好像类本来就有这些属性和方法一样。 而且不需要写一个新的类去继承或包含现有类。
Yii自带的 yii\behaviors\AttributeBehavior 类,定义了在一个 ActiveRecord 对象的某些事件发生时, 自动对某些字段进行修改的行为。 他有一个很常用的子类yii\behaviors\TimeStampBehavior 用于将指定的字段设置为一个当前的时间戳。 常用于表示最后修改日期、上次登陆时间等场景。我们以这个行为为例,来分析行为响应事件的原理。
在 yii\behaviors\AttributeBehavior::events() 中,代码如下:
public function events()
{
return array_fill_keys(array_keys($this->attributes),
'evaluateAttributes');
}
这段代码将返回一个数组,其键值为 $this->attributes 数组的键值, 数组元素的值为成员函数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 = [
BaseActiveRecord::EVENT_BEFORE_INSERT => [created_at, updated_at],
BaseActiveRecord::EVENT_BEFORE_UPDATE => updated_at,
];
}
}
上面的代码重点看的是对于 $this->attributes 的初始化部分。 结合上面2个方法的代码,对于 yii\behaviors\AttributeBehavior::event()的返回数组,其格式应该是这样的:
return [
BaseActiveRecord::EVENT_BEFORE_INSERT => 'evaluateAttributes',
BaseActiveRecord::EVENT_BEFORE_UPDATE => 'evaluateAttributes',
];
数组的键值用于指定要响应的事件, 这里是
BaseActiveRecord::EVENT_BEFORE_INSERT 和
BaseActiveRecord::EVENT_BEFORE_UPDATE 。 数组的值是一个事件handler,如上面的
evaluateAttributes
// 这里 $owner 是某个 ActiveRecord
public function attach($owner)
{
$this->owner = $owner;
// 遍历上面提到的 events() 所定义的数组
foreach ($this->events() as $event => $handler) {
// 调用 ActiveRecord::on 来绑定事件
// 这里 $handler 为字符串 `evaluateAttributes`
// 因此,相当于调用 on(BaseActiveRecord::EVENT_BEFORE_INSERT,
// [$this, 'evaluateAttributes'])
$owner->on($event, is_string($handler) ? [$this, $handler] :
$handler);
}
}
上面的代码干了两件事:
- 设置好行为的 $owner ,使得行为可以访问、操作所依附的对象
- 遍历行为中的 events() 返回的数组,将准备响应的事件,通过所依附类的 on() 绑定到类上
因此,事件 BaseActiveRecord::EVENT_BEFORE_INSERT 和BaseActiveRecord::EVENT_BEFORE_UPDATE 就绑定到了ActiveRecord上了。当新建记录或更新记录时,TimeStampBehavior::evaluateAttributes 就会被触发。 从而实现时间戳的功能,
例如: 当记录插入时,行为将当前时间戳赋值给 created_at 和 updated_at 属性;
当记录更新时,行为将当前时间戳赋值给 updated_at 属性。
保存对象,将会发现它的 created_at 和 updated_at 属性自动填充了当前时间戳。
具体可以看看 yii\behaviors\AttributeBehavior::evaluateAttributes() 和yii\behaviors\TimeStampBehavior::getValues() 的代码/**
* Evaluates the attribute value and assigns it to the current attributes.
* @param Event $event
*/
public function evaluateAttributes($event)
{
if (!empty($this->attributes[$event->name])) { //例如我当前是插入操作,那么$event->name 就等于 'beforeInsert';
$attributes = (array) $this->attributes[$event->name];
$value = $this->getValue($event);
foreach ($attributes as $attribute) {
// ignore attribute names which are not string (e.g. when set by TimestampBehavior::updatedAtAttribute)
if (is_string($attribute)) {
$this->owner->$attribute = $value;
}
}
}
}
/**
* @inheritdoc
*/
protected function getValue($event)
{
if ($this->value instanceof Expression) {
return $this->value;
} else {
return $this->value !== null ? call_user_func($this->value, $event) : time();
}
}