Yii2 TimestampBehavior行为
1 <?php 2 /** 3 * @link http://www.yiiframework.com/ 4 * @copyright Copyright (c) 2008 Yii Software LLC 5 * @license http://www.yiiframework.com/license/ 6 */ 7 8 namespace yii\behaviors; 9 10 use yii\base\InvalidCallException; 11 use yii\db\BaseActiveRecord; 12 13 /** 14 * TimestampBehavior automatically fills the specified attributes with the current timestamp. 15 * 16 * To use TimestampBehavior, insert the following code to your ActiveRecord class: 17 * 18 * ```php 19 * use yii\behaviors\TimestampBehavior; 20 * 21 * public function behaviors() 22 * { 23 * return [ 24 * TimestampBehavior::className(), 25 * ]; 26 * } 27 * ``` 28 * 29 * By default, TimestampBehavior will fill the `created_at` and `updated_at` attributes with the current timestamp 30 * when the associated AR object is being inserted; it will fill the `updated_at` attribute 31 * with the timestamp when the AR object is being updated. The timestamp value is obtained by `time()`. 32 * 33 * Because attribute values will be set automatically, it's a good idea to make sure `created_at` and `updated_at` aren't 34 * in `rules()` method of the model. 35 * 36 * For the above implementation to work with MySQL database, please declare the columns(`created_at`, `updated_at`) as int(11) for being UNIX timestamp. 37 * 38 * If your attribute names are different or you want to use a different way of calculating the timestamp, 39 * you may configure the [[createdAtAttribute]], [[updatedAtAttribute]] and [[value]] properties like the following: 40 * 41 * ```php 42 * use yii\db\Expression; 43 * 44 * public function behaviors() 45 * { 46 * return [ 47 * [ 48 * 'class' => TimestampBehavior::className(), 49 * 'createdAtAttribute' => 'create_time', 50 * 'updatedAtAttribute' => 'update_time', 51 * 'value' => new Expression('NOW()'), 52 * ], 53 * ]; 54 * } 55 * ``` 56 * 57 * In case you use an [[\yii\db\Expression]] object as in the example above, the attribute will not hold the timestamp value, but 58 * the Expression object itself after the record has been saved. If you need the value from DB afterwards you should call 59 * the [[\yii\db\ActiveRecord::refresh()|refresh()]] method of the record. 60 * 61 * TimestampBehavior also provides a method named [[touch()]] that allows you to assign the current 62 * timestamp to the specified attribute(s) and save them to the database. For example, 63 * 64 * ```php 65 * $model->touch('creation_time'); 66 * ``` 67 * 68 * @author Qiang Xue <qiang.xue@gmail.com> 69 * @author Alexander Kochetov <creocoder@gmail.com> 70 * @since 2.0 71 */ 72 class TimestampBehavior extends AttributeBehavior 73 { 74 /** 75 * @var string the attribute that will receive timestamp value 76 * Set this property to false if you do not want to record the creation time. 77 */ 78 public $createdAtAttribute = 'created_at'; 79 /** 80 * @var string the attribute that will receive timestamp value. 81 * Set this property to false if you do not want to record the update time. 82 */ 83 public $updatedAtAttribute = 'updated_at'; 84 /** 85 * @inheritdoc 86 * 87 * In case, when the value is `null`, the result of the PHP function [time()](http://php.net/manual/en/function.time.php) 88 * will be used as value. 89 */ 90 public $value; 91 92 93 /** 94 * @inheritdoc 95 */ 96 public function init() 97 { 98 parent::init(); 99 100 if (empty($this->attributes)) { 101 $this->attributes = [ 102 BaseActiveRecord::EVENT_BEFORE_INSERT => [$this->createdAtAttribute, $this->updatedAtAttribute], 103 BaseActiveRecord::EVENT_BEFORE_UPDATE => $this->updatedAtAttribute, 104 ]; 105 } 106 } 107 108 /** 109 * @inheritdoc 110 * 111 * In case, when the [[value]] is `null`, the result of the PHP function [time()](http://php.net/manual/en/function.time.php) 112 * will be used as value. 113 */ 114 protected function getValue($event) 115 { 116 if ($this->value === null) { 117 returntime(); 118 } 119 return parent::getValue($event); 120 } 121 122 /** 123 * Updates a timestamp attribute to the current timestamp. 124 * 125 * ```php 126 * $model->touch('lastVisit'); 127 * ``` 128 * @param string $attribute the name of the attribute to update. 129 * @throws InvalidCallException if owner is a new record (since version 2.0.6). 130 */ 131 public function touch($attribute) 132 { 133 /* @var $owner BaseActiveRecord */ 134 $owner = $this->owner; 135 if ($owner->getIsNewRecord()) { 136 throw new InvalidCallException('Updating the timestamp is not possible on a new record.'); 137 } 138 $owner->updateAttributes(array_fill_keys((array) $attribute, $this->getValue(null))); 139 } 140 }
TimestampBehavior 里面的public function init(){}初始化绑定事件处理的方法数据
1 /** 2 * @inheritdoc 3 */ 4 public function init() 5 { 6 parent::init(); 7 8 if (empty($this->attributes)) { 9 $this->attributes = [ 10 BaseActiveRecord::EVENT_BEFORE_INSERT => [$this->createdAtAttribute, $this->updatedAtAttribute], 11 BaseActiveRecord::EVENT_BEFORE_UPDATE => $this->updatedAtAttribute, 12 ]; 13 } 14 }
TimestampBehavior 父级AttributeBehavior 里面events()方法 为事件绑定执行handle :evaluateAttributes方法
1 /** 2 * @inheritdoc 3 */ 4 public function events() 5 { 6 return array_fill_keys( 7 array_keys($this->attributes), 8 'evaluateAttributes' 9 ); 10 }
事件绑定的方法:evaluateAttributes方法,
1 /** 2 * Evaluates the attribute value and assigns it to the current attributes. 3 * @param Event $event 4 */ 5 public function evaluateAttributes($event) 6 { 7 if ($this->skipUpdateOnClean 8 && $event->name == ActiveRecord::EVENT_BEFORE_UPDATE 9 && empty($this->owner->dirtyAttributes) 10 ) { 11 return; 12 } 13 14 if (!empty($this->attributes[$event->name])) { 15 $attributes = (array) $this->attributes[$event->name]; 16 $value = $this->getValue($event); 17 foreach ($attributes as $attribute) { 18 // ignore attribute names which are not string (e.g. when set by TimestampBehavior::updatedAtAttribute) 19 if (is_string($attribute)) { 20 $this->owner->$attribute = $value; 21 } 22 } 23 } 24 }
当update 或者insert save数据时候,BaseActiveRecord.php 里面的save():
1 /** 2 * Saves the current record. 3 * 4 * This method will call [[insert()]] when [[isNewRecord]] is true, or [[update()]] 5 * when [[isNewRecord]] is false. 6 * 7 * For example, to save a customer record: 8 * 9 * ```php 10 * $customer = new Customer; // or $customer = Customer::findOne($id); 11 * $customer->name = $name; 12 * $customer->email = $email; 13 * $customer->save(); 14 * ``` 15 * 16 * @param boolean $runValidation whether to perform validation (calling [[validate()]]) 17 * before saving the record. Defaults to `true`. If the validation fails, the record 18 * will not be saved to the database and this method will return `false`. 19 * @param array $attributeNames list of attribute names that need to be saved. Defaults to null, 20 * meaning all attributes that are loaded from DB will be saved. 21 * @return boolean whether the saving succeeded (i.e. no validation errors occurred). 22 */ 23 public function save($runValidation = true, $attributeNames = null) 24 { 25 if ($this->getIsNewRecord()) { 26 return $this->insert($runValidation, $attributeNames); 27 } else { 28 return $this->update($runValidation, $attributeNames) !== false; 29 } 30 }
调用ActiveRecord.php 里面的insert() 和update ():
1 public function insert($runValidation = true, $attributes = null) 2 { 3 if ($runValidation && !$this->validate($attributes)) { 4 Yii::info('Model not inserted due to validation error.', __METHOD__); 5 return false; 6 } 7 8 if (!$this->isTransactional(self::OP_INSERT)) { 9 return $this->insertInternal($attributes); 10 } 11 12 $transaction = static::getDb()->beginTransaction(); 13 try { 14 $result = $this->insertInternal($attributes); 15 if ($result === false) { 16 $transaction->rollBack(); 17 } else { 18 $transaction->commit(); 19 } 20 return $result; 21 } catch (\Exception $e) { 22 $transaction->rollBack(); 23 throw $e; 24 } 25 } 26 public function update($runValidation = true, $attributeNames = null) 27 { 28 if ($runValidation && !$this->validate($attributeNames)) { 29 Yii::info('Model not updated due to validation error.', __METHOD__); 30 return false; 31 } 32 33 if (!$this->isTransactional(self::OP_UPDATE)) { 34 return $this->updateInternal($attributeNames); 35 } 36 37 $transaction = static::getDb()->beginTransaction(); 38 try { 39 $result = $this->updateInternal($attributeNames); 40 if ($result === false) { 41 $transaction->rollBack(); 42 } else { 43 $transaction->commit(); 44 } 45 return $result; 46 } catch (\Exception $e) { 47 $transaction->rollBack(); 48 throw $e; 49 } 50 }
调用ActiveRecord.php 里面insertInternal() 和updateInternal():
1 /** 2 * Inserts an ActiveRecord into DB without considering transaction. 3 * @param array $attributes list of attributes that need to be saved. Defaults to null, 4 * meaning all attributes that are loaded from DB will be saved. 5 * @return boolean whether the record is inserted successfully. 6 */ 7 protected function insertInternal($attributes = null) 8 { 9 if (!$this->beforeSave(true)) { 10 return false; 11 } 12 $values = $this->getDirtyAttributes($attributes); 13 if (($primaryKeys = static::getDb()->schema->insert(static::tableName(), $values)) === false) { 14 return false; 15 } 16 foreach ($primaryKeys as $name => $value) { 17 $id = static::getTableSchema()->columns[$name]->phpTypecast($value); 18 $this->setAttribute($name, $id); 19 $values[$name] = $id; 20 } 21 22 $changedAttributes = array_fill_keys(array_keys($values), null); 23 $this->setOldAttributes($values); 24 $this->afterSave(true, $changedAttributes); 25 26 return true; 27 } 28 /** 29 * @see update() 30 * @param array $attributes attributes to update 31 * @return integer number of rows updated 32 * @throws StaleObjectException 33 */ 34 protected function updateInternal($attributes = null) 35 { 36 if (!$this->beforeSave(false)) { 37 return false; 38 } 39 $values = $this->getDirtyAttributes($attributes); 40 if (empty($values)) { 41 $this->afterSave(false, $values); 42 return 0; 43 } 44 $condition = $this->getOldPrimaryKey(true); 45 $lock = $this->optimisticLock(); 46 if ($lock !== null) { 47 $values[$lock] = $this->$lock + 1; 48 $condition[$lock] = $this->$lock; 49 } 50 // We do not check the return value of updateAll() because it's possible 51 // that the UPDATE statement doesn't change anything and thus returns 0. 52 $rows = static::updateAll($values, $condition); 53 54 if ($lock !== null && !$rows) { 55 throw new StaleObjectException('The object being updated is outdated.'); 56 } 57 58 if (isset($values[$lock])) { 59 $this->$lock = $values[$lock]; 60 } 61 62 $changedAttributes = []; 63 foreach ($values as $name => $value) { 64 $changedAttributes[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null; 65 $this->_oldAttributes[$name] = $value; 66 } 67 $this->afterSave(false, $changedAttributes); 68 69 return $rows; 70 }
调用BaseActiveRecord.php 里面的beforeSave(),触发$this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event)事件执行添加之前绑定的事件方法evaluateAttributes自动相关添加属性值:
1 public function beforeSave($insert) 2 { 3 $event = new ModelEvent; 4 $this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event); 5 6 return $event->isValid; 7 }
}