Commit 02d5b20b by Qiang Xue

Fixes #2360: Added `AttributeBehavior` and `BlameableBehavior`, and renamed…

Fixes #2360: Added `AttributeBehavior` and `BlameableBehavior`, and renamed `AutoTimestamp` to `TimestampBehavior`
parent cd07a416
......@@ -197,6 +197,7 @@ Yii Framework 2 Change Log
- New #1438: [MongoDB integration](https://github.com/yiisoft/yii2-mongodb) ActiveRecord and Query (klimov-paul)
- New #1956: Implemented test fixture framework (qiangxue)
- New #2149: Added `yii\base\DynamicModel` to support ad-hoc data validation (qiangxue)
- New #2360: Added `AttributeBehavior` and `BlameableBehavior`, and renamed `AutoTimestamp` to `TimestampBehavior` (lucianobaraglia, qiangxue)
- New: Yii framework now comes with core messages in multiple languages
- New: Added yii\codeception\DbTestCase (qiangxue)
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\behaviors;
use Yii;
use Closure;
use yii\base\Behavior;
use yii\base\Event;
/**
* AttributeBehavior automatically assigns a specified value to one or multiple attributes of an ActiveRecord object when certain events happen.
*
* To use AttributeBehavior, 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. For example,
* Then configure the [[value]] property with a PHP callable whose return value will be used to assign to the current
* attribute(s). For example,
*
* ~~~
* public function behaviors()
* {
* return [
* 'attributeStamp' => [
* 'class' => 'yii\behaviors\AttributeBehavior',
* 'attributes' => [
* ActiveRecord::EVENT_BEFORE_INSERT => ['attribute1'],
* ActiveRecord::EVENT_BEFORE_UPDATE => ['attribute2'],
* ],
* 'value' => function ($event) {
* return 'some value';
* },
* ],
* ];
* }
* ~~~
*
* @author Luciano Baraglia <luciano.baraglia@gmail.com>
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class AttributeBehavior extends Behavior
{
/**
* @var array list of attributes that are to be automatically filled with the value specified via [[value]].
* The array keys are the ActiveRecord events upon which the attributes are to be updated,
* and the array values are the corresponding attribute(s) to be updated. You can use a string to represent
* a single attribute, or an array to represent a list of attributes. For example,
*
* ```php
* [
* ActiveRecord::EVENT_BEFORE_INSERT => ['attribute1', 'attribute2'],
* ActiveRecord::EVENT_BEFORE_UPDATE => 'attribute2',
* ]
* ```
*/
public $attributes = [];
/**
* @var mixed the value that will be assigned to the current attributes. This can be an anonymous function
* or an arbitrary value. If the former, the return value of the function will be assigned to the attributes.
* The signature of the function should be as follows,
*
* ```php
* function ($event) {
* // return value will be assigned to the attribute
* }
* ```
*/
public $value;
/**
* @inheritdoc
*/
public function events()
{
return array_fill_keys(array_keys($this->attributes), 'evaluateAttributes');
}
/**
* Evaluates the attribute value and assigns it to the current attributes.
* @param $event
*/
public function evaluateAttributes($event)
{
if (!empty($this->attributes[$event->name])) {
$attributes = (array)$this->attributes[$event->name];
$value = $this->getValue($event);
foreach ($attributes as $attribute) {
$this->owner->$attribute = $value;
}
}
}
/**
* Returns the value of the current attributes.
* This method is called by [[evaluateAttributes()]]. Its return value will be assigned
* to the attributes corresponding to the triggering event.
* @param Event $event the event that triggers the current attribute updating.
* @return mixed the attribute value
*/
protected function getValue($event)
{
return $this->value instanceof Closure ? call_user_func($this->value, $event) : $this->value;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\behaviors;
use yii\base\Behavior;
use yii\base\Event;
use yii\db\Expression;
use yii\db\ActiveRecord;
/**
* AutoTimestamp will automatically fill the attributes about creation time and updating time.
*
* AutoTimestamp fills the attributes when the associated AR model is being inserted or updated.
* You may specify an AR to use this behavior like the following:
*
* ~~~
* public function behaviors()
* {
* return [
* 'timestamp' => ['class' => 'yii\behaviors\AutoTimestamp'],
* ];
* }
* ~~~
*
* By default, AutoTimestamp will fill the `created_at` attribute with the current timestamp
* when the associated AR object is being inserted; it will fill the `updated_at` attribute
* with the timestamp when the AR object is being updated.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class AutoTimestamp extends Behavior
{
/**
* @var array list of attributes that are to be automatically filled with timestamps.
* The array keys are the ActiveRecord events upon which the attributes are to be filled with timestamps,
* and the array values are the corresponding attribute(s) to be updated. You can use a string to represent
* a single attribute, or an array to represent a list of attributes.
* The default setting is to update the `created_at` attribute upon AR insertion,
* and update the `updated_at` attribute upon AR updating.
*/
public $attributes = [
ActiveRecord::EVENT_BEFORE_INSERT => 'created_at',
ActiveRecord::EVENT_BEFORE_UPDATE => 'updated_at',
];
/**
* @var \Closure|Expression The expression that will be used for generating the timestamp.
* This can be either an anonymous function that returns the timestamp value,
* or an [[Expression]] object representing a DB expression (e.g. `new Expression('NOW()')`).
* If not set, it will use the value of `time()` to fill the attributes.
*/
public $timestamp;
/**
* Declares event handlers for the [[owner]]'s events.
* @return array events (array keys) and the corresponding event handler methods (array values).
*/
public function events()
{
$events = $this->attributes;
foreach ($events as $i => $event) {
$events[$i] = 'updateTimestamp';
}
return $events;
}
/**
* Updates the attributes with the current timestamp.
* @param Event $event
*/
public function updateTimestamp($event)
{
$attributes = isset($this->attributes[$event->name]) ? (array)$this->attributes[$event->name] : [];
if (!empty($attributes)) {
$timestamp = $this->evaluateTimestamp();
foreach ($attributes as $attribute) {
$this->owner->$attribute = $timestamp;
}
}
}
/**
* Gets the current timestamp.
* @return mixed the timestamp value
*/
protected function evaluateTimestamp()
{
if ($this->timestamp instanceof Expression) {
return $this->timestamp;
} elseif ($this->timestamp !== null) {
return call_user_func($this->timestamp);
} else {
return time();
}
}
/**
* Updates a timestamp attribute to the current timestamp.
*
* ```php
* $model->touch('lastVisit');
* ```
* @param string $attribute the name of the attribute to update.
*/
public function touch($attribute)
{
$timestamp = $this->evaluateTimestamp();
$this->owner->updateAttributes([$attribute => $timestamp]);
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\behaviors;
use Yii;
use yii\base\Event;
use yii\db\BaseActiveRecord;
/**
* BlameableBehavior automatically fills the specified attributes with the current user ID.
*
* To use BlameableBehavior, simply insert the following code to your ActiveRecord class:
*
* ```php
* use yii\behaviors\BlameableBehavior;
*
* public function behaviors()
* {
* return [
* BlameableBehavior::className(),
* ];
* }
* ```
*
* By default, BlameableBehavior will fill the `created_by` and `updated_by` attributes with the current user ID
* when the associated AR object is being inserted; it will fill the `updated_by` attribute
* with the current user ID when the AR object is being updated. If your attribute names are different, you may configure
* the [[attributes]] property like the following:
*
* ```php
* public function behaviors()
* {
* return [
* [
* 'class' => BlameableBehavior::className(),
* 'attributes' => [
* ActiveRecord::EVENT_BEFORE_INSERT => ['creator_id'],
* ActiveRecord::EVENT_BEFORE_UPDATE => ['updater_id'],
* ],
* ],
* ];
* }
* ```
*
* @author Luciano Baraglia <luciano.baraglia@gmail.com>
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class BlameableBehavior extends AttributeBehavior
{
/**
* @var array list of attributes that are to be automatically filled with the current user ID.
* The array keys are the ActiveRecord events upon which the attributes are to be filled with the user ID,
* and the array values are the corresponding attribute(s) to be updated. You can use a string to represent
* a single attribute, or an array to represent a list of attributes.
* The default setting is to update both of the `created_by` and `updated_by` attributes upon AR insertion,
* and update the `updated_by` attribute upon AR updating.
*/
public $attributes = [
BaseActiveRecord::EVENT_BEFORE_INSERT => ['created_by', 'updated_by'],
BaseActiveRecord::EVENT_BEFORE_UPDATE => ['updated_by'],
];
/**
* @var callable the value that will be assigned to the attributes. This should be a valid
* PHP callable whose return value will be assigned to the current attribute(s).
* The signature of the callable should be:
*
* ```php
* function ($event) {
* // return value will be assigned to the attribute(s)
* }
* ```
*
* If this property is not set, the value of `Yii::$app->user->id` will be assigned to the attribute(s).
*/
public $value;
/**
* Evaluates the value of the user.
* The return result of this method will be assigned to the current attribute(s).
* @param Event $event
* @return mixed the value of the user.
*/
protected function getValue($event)
{
if ($this->value === null) {
$user = Yii::$app->getUser();
return $user && !$user->isGuest ? $user->id : null;
} else {
return call_user_func($this->value, $event);
}
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\behaviors;
use yii\db\BaseActiveRecord;
use yii\db\Expression;
/**
* TimestampBehavior automatically fills the specified attributes with the current timestamp.
*
* To use TimestampBehavior, simply insert the following code to your ActiveRecord class:
*
* ```php
* use yii\behaviors\TimestampBehavior;
*
* public function behaviors()
* {
* return [
* TimestampBehavior::className(),
* ];
* }
* ```
*
* By default, TimestampBehavior will fill the `created_at` and `updated_at` attributes with the current timestamp
* when the associated AR object is being inserted; it will fill the `updated_at` attribute
* with the timestamp when the AR object is being updated. The timestamp value is obtained by `time()`.
*
* If your attribute names are different or you want to use a different way of calculating the timestamp,
* you may configure the [[attributes]] and [[value]] properties like the following:
*
* ```php
* use yii\behaviors\TimestampBehavior;
* use yii\db\Expression;
*
* public function behaviors()
* {
* return [
* 'timestamp' => [
* 'class' => TimestampBehavior::className(),
* 'attributes' => [
* ActiveRecord::EVENT_BEFORE_INSERT => ['creation_time'],
* ActiveRecord::EVENT_BEFORE_UPDATE => ['update_time'],
* ],
* 'value' => new Expression('NOW()'),
* ],
* ];
* }
* ```
*
* TimestampBehavior also provides a method named [[touch()]] that allows you to assign the current
* timestamp to the specified attribute(s) and save them to the database. For example,
*
* ```php
* $this->timestamp->touch('creation_time');
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class TimestampBehavior extends AttributeBehavior
{
/**
* @var array list of attributes that are to be automatically filled with timestamps.
* The array keys are the ActiveRecord events upon which the attributes are to be filled with timestamps,
* and the array values are the corresponding attribute(s) to be updated. You can use a string to represent
* a single attribute, or an array to represent a list of attributes.
* The default setting is to update both of the `created_at` and `updated_at` attributes upon AR insertion,
* and update the `updated_at` attribute upon AR updating.
*/
public $attributes = [
BaseActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
BaseActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
];
/**
* @var callable|Expression The expression that will be used for generating the timestamp.
* This can be either an anonymous function that returns the timestamp value,
* or an [[Expression]] object representing a DB expression (e.g. `new Expression('NOW()')`).
* If not set, it will use the value of `time()` to set the attributes.
*/
public $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();
}
}
/**
* Updates a timestamp attribute to the current timestamp.
*
* ```php
* $model->touch('lastVisit');
* ```
* @param string $attribute the name of the attribute to update.
*/
public function touch($attribute)
{
$this->owner->updateAttributes(array_fill_keys((array)$attribute, $this->getValue(null)));
}
}
......@@ -6,15 +6,15 @@ use Yii;
use yiiunit\TestCase;
use yii\db\Connection;
use yii\db\ActiveRecord;
use yii\behaviors\AutoTimestamp;
use yii\behaviors\TimestampBehavior;
/**
* Unit test for [[\yii\behaviors\AutoTimestamp]].
* Unit test for [[\yii\behaviors\TimestampBehavior]].
* @see AutoTimestamp
*
* @group behaviors
*/
class AutoTimestampTest extends TestCase
class TimestampBehaviorTest extends TestCase
{
/**
* @var Connection test db connection
......@@ -59,7 +59,7 @@ class AutoTimestampTest extends TestCase
{
$currentTime = time();
$model = new ActiveRecordAutoTimestamp();
$model = new ActiveRecordTimestamp();
$model->save(false);
$this->assertTrue($model->created_at >= $currentTime);
......@@ -73,7 +73,7 @@ class AutoTimestampTest extends TestCase
{
$currentTime = time();
$model = new ActiveRecordAutoTimestamp();
$model = new ActiveRecordTimestamp();
$model->save(false);
$enforcedTime = $currentTime - 100;
......@@ -94,18 +94,12 @@ class AutoTimestampTest extends TestCase
* @property integer $created_at
* @property integer $updated_at
*/
class ActiveRecordAutoTimestamp extends ActiveRecord
class ActiveRecordTimestamp extends ActiveRecord
{
public function behaviors()
{
return [
'timestamp' => [
'class' => AutoTimestamp::className(),
'attributes' => [
static::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
static::EVENT_BEFORE_UPDATE => 'updated_at',
],
],
TimestampBehavior::className(),
];
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment