Commit 48f6e380 by Carsten Brandt

Merge PR #4038 branch 'consistent-after-save'

* consistent-after-save: use an AfterSaveEvent class to be consistent fixed test break and wrong parameter changed updateAttributes to be more simple update Ensure consistent behavior in ActiveRecord::afterSave() Conflicts: framework/UPGRADE.md
parents 775141ab edb95052
...@@ -395,35 +395,31 @@ class ActiveRecord extends BaseActiveRecord ...@@ -395,35 +395,31 @@ class ActiveRecord extends BaseActiveRecord
if ($runValidation && !$this->validate($attributes)) { if ($runValidation && !$this->validate($attributes)) {
return false; return false;
} }
if ($this->beforeSave(true)) { if (!$this->beforeSave(true)) {
$values = $this->getDirtyAttributes($attributes); return false;
}
$response = static::getDb()->createCommand()->insert( $values = $this->getDirtyAttributes($attributes);
static::index(),
static::type(),
$values,
$this->getPrimaryKey(),
$options
);
// if (!isset($response['ok'])) {
// return false;
// }
$pk = static::primaryKey()[0];
$this->$pk = $response['_id'];
if ($pk != '_id') {
$values[$pk] = $response['_id'];
}
$this->_version = $response['_version'];
$this->_score = null;
$this->afterSave(true); $response = static::getDb()->createCommand()->insert(
$this->setOldAttributes($values); static::index(),
static::type(),
$values,
$this->getPrimaryKey(),
$options
);
return true; $pk = static::primaryKey()[0];
$this->$pk = $response['_id'];
if ($pk != '_id') {
$values[$pk] = $response['_id'];
} }
$this->_version = $response['_version'];
$this->_score = null;
$this->setOldAttributes($values);
$this->afterSave(true, $values);
return false; return true;
} }
/** /**
......
...@@ -224,8 +224,8 @@ abstract class ActiveRecord extends BaseActiveRecord ...@@ -224,8 +224,8 @@ abstract class ActiveRecord extends BaseActiveRecord
$this->setAttribute('_id', $newId); $this->setAttribute('_id', $newId);
$values['_id'] = $newId; $values['_id'] = $newId;
$this->afterSave(true);
$this->setOldAttributes($values); $this->setOldAttributes($values);
$this->afterSave(true, $values);
return true; return true;
} }
...@@ -241,7 +241,7 @@ abstract class ActiveRecord extends BaseActiveRecord ...@@ -241,7 +241,7 @@ abstract class ActiveRecord extends BaseActiveRecord
} }
$values = $this->getDirtyAttributes($attributes); $values = $this->getDirtyAttributes($attributes);
if (empty($values)) { if (empty($values)) {
$this->afterSave(false); $this->afterSave(false, $values);
return 0; return 0;
} }
$condition = $this->getOldPrimaryKey(true); $condition = $this->getOldPrimaryKey(true);
...@@ -260,10 +260,10 @@ abstract class ActiveRecord extends BaseActiveRecord ...@@ -260,10 +260,10 @@ abstract class ActiveRecord extends BaseActiveRecord
throw new StaleObjectException('The object being updated is outdated.'); throw new StaleObjectException('The object being updated is outdated.');
} }
$this->afterSave(false);
foreach ($values as $name => $value) { foreach ($values as $name => $value) {
$this->setOldAttribute($name, $this->getAttribute($name)); $this->setOldAttribute($name, $this->getAttribute($name));
} }
$this->afterSave(false, $values);
return $rows; return $rows;
} }
......
...@@ -127,8 +127,8 @@ abstract class ActiveRecord extends \yii\mongodb\ActiveRecord ...@@ -127,8 +127,8 @@ abstract class ActiveRecord extends \yii\mongodb\ActiveRecord
$this->setAttribute('_id', $newId); $this->setAttribute('_id', $newId);
$values['_id'] = $newId; $values['_id'] = $newId;
$this->afterSave(true);
$this->setOldAttributes($values); $this->setOldAttributes($values);
$this->afterSave(true, $values);
return true; return true;
} }
...@@ -144,7 +144,7 @@ abstract class ActiveRecord extends \yii\mongodb\ActiveRecord ...@@ -144,7 +144,7 @@ abstract class ActiveRecord extends \yii\mongodb\ActiveRecord
} }
$values = $this->getDirtyAttributes($attributes); $values = $this->getDirtyAttributes($attributes);
if (empty($values)) { if (empty($values)) {
$this->afterSave(false); $this->afterSave(false, $values);
return 0; return 0;
} }
...@@ -196,10 +196,10 @@ abstract class ActiveRecord extends \yii\mongodb\ActiveRecord ...@@ -196,10 +196,10 @@ abstract class ActiveRecord extends \yii\mongodb\ActiveRecord
} }
} }
$this->afterSave(false);
foreach ($values as $name => $value) { foreach ($values as $name => $value) {
$this->setOldAttribute($name, $this->getAttribute($name)); $this->setOldAttribute($name, $this->getAttribute($name));
} }
$this->afterSave(false, $values);
return $rows; return $rows;
} }
......
...@@ -99,38 +99,35 @@ class ActiveRecord extends BaseActiveRecord ...@@ -99,38 +99,35 @@ class ActiveRecord extends BaseActiveRecord
if ($runValidation && !$this->validate($attributes)) { if ($runValidation && !$this->validate($attributes)) {
return false; return false;
} }
if ($this->beforeSave(true)) { if (!$this->beforeSave(true)) {
$db = static::getDb(); return false;
$values = $this->getDirtyAttributes($attributes); }
$pk = []; $db = static::getDb();
// if ($values === []) { $values = $this->getDirtyAttributes($attributes);
foreach ($this->primaryKey() as $key) { $pk = [];
$pk[$key] = $values[$key] = $this->getAttribute($key); foreach ($this->primaryKey() as $key) {
if ($pk[$key] === null) { $pk[$key] = $values[$key] = $this->getAttribute($key);
$pk[$key] = $values[$key] = $db->executeCommand('INCR', [static::keyPrefix() . ':s:' . $key]); if ($pk[$key] === null) {
$this->setAttribute($key, $values[$key]); $pk[$key] = $values[$key] = $db->executeCommand('INCR', [static::keyPrefix() . ':s:' . $key]);
} $this->setAttribute($key, $values[$key]);
}
// }
// save pk in a findall pool
$db->executeCommand('RPUSH', [static::keyPrefix(), static::buildKey($pk)]);
$key = static::keyPrefix() . ':a:' . static::buildKey($pk);
// save attributes
$args = [$key];
foreach ($values as $attribute => $value) {
$args[] = $attribute;
$args[] = $value;
} }
$db->executeCommand('HMSET', $args); }
// save pk in a findall pool
$this->afterSave(true); $db->executeCommand('RPUSH', [static::keyPrefix(), static::buildKey($pk)]);
$this->setOldAttributes($values);
return true; $key = static::keyPrefix() . ':a:' . static::buildKey($pk);
// save attributes
$args = [$key];
foreach ($values as $attribute => $value) {
$args[] = $attribute;
$args[] = $value;
} }
$db->executeCommand('HMSET', $args);
$this->setOldAttributes($values);
$this->afterSave(true, $values);
return false; return true;
} }
/** /**
......
...@@ -395,8 +395,8 @@ abstract class ActiveRecord extends BaseActiveRecord ...@@ -395,8 +395,8 @@ abstract class ActiveRecord extends BaseActiveRecord
return false; return false;
} }
$this->afterSave(true);
$this->setOldAttributes($values); $this->setOldAttributes($values);
$this->afterSave(true, $values);
return true; return true;
} }
...@@ -488,7 +488,7 @@ abstract class ActiveRecord extends BaseActiveRecord ...@@ -488,7 +488,7 @@ abstract class ActiveRecord extends BaseActiveRecord
} }
$values = $this->getDirtyAttributes($attributes); $values = $this->getDirtyAttributes($attributes);
if (empty($values)) { if (empty($values)) {
$this->afterSave(false); $this->afterSave(false, $values);
return 0; return 0;
} }
...@@ -530,10 +530,10 @@ abstract class ActiveRecord extends BaseActiveRecord ...@@ -530,10 +530,10 @@ abstract class ActiveRecord extends BaseActiveRecord
} }
} }
$this->afterSave(false);
foreach ($values as $name => $value) { foreach ($values as $name => $value) {
$this->setOldAttribute($name, $this->getAttribute($name)); $this->setOldAttribute($name, $this->getAttribute($name));
} }
$this->afterSave(false, $values);
return $rows; return $rows;
} }
......
...@@ -22,6 +22,7 @@ Yii Framework 2 Change Log ...@@ -22,6 +22,7 @@ Yii Framework 2 Change Log
- Bug #3194: Date formatter works only for timestamps in the year range 1970 to 2038 (kartik-v) - Bug #3194: Date formatter works only for timestamps in the year range 1970 to 2038 (kartik-v)
- Bug #3204: `yii\di\Container` did not handle the `$config` parameter well in case when it does not have a default value (qiangxue) - Bug #3204: `yii\di\Container` did not handle the `$config` parameter well in case when it does not have a default value (qiangxue)
- Bug #3216: Fixed the bug that `yii.activeForm.destroy()` did not remove `submit` event handlers (qiangxue) - Bug #3216: Fixed the bug that `yii.activeForm.destroy()` did not remove `submit` event handlers (qiangxue)
- Bug #3233: Ensure consistent behavior in ActiveRecord::afterSave() (cebe, qiangxue)
- Bug #3236: Return value for DateTime->format('U') casted to double to allow correct date formatting (pgaultier) - Bug #3236: Return value for DateTime->format('U') casted to double to allow correct date formatting (pgaultier)
- Bug #3268: Fixed the bug that the schema name in a table name was not respected by `yii\db\mysql\Schema` (terazoid, qiangxue) - Bug #3268: Fixed the bug that the schema name in a table name was not respected by `yii\db\mysql\Schema` (terazoid, qiangxue)
- Bug #3311: Fixed the bug that `yii\di\Container::has()` did not return correct value (mgrechanik, qiangxue) - Bug #3311: Fixed the bug that `yii\di\Container::has()` did not return correct value (mgrechanik, qiangxue)
......
...@@ -57,3 +57,12 @@ Upgrade from Yii 2.0 Beta ...@@ -57,3 +57,12 @@ Upgrade from Yii 2.0 Beta
* If your model code is using the `file` validation rule, you should rename its `types` option to `extensions`. * If your model code is using the `file` validation rule, you should rename its `types` option to `extensions`.
* `MailEvent` class has been moved to the `yii\mail` namespace. You have to adjust all references that may exist in your code. * `MailEvent` class has been moved to the `yii\mail` namespace. You have to adjust all references that may exist in your code.
* The behavior and signature of `ActiveRecord::afterSave()` has changed. `ActiveRecord::$isNewRecord` will now always be
false in afterSave and also dirty attributes are not available. This change has been made to have a more consistent and
expected behavior. The changed attributes are now available in the new parameter of afterSave() `$changedAttributes`.
* `ActiveRecord::updateAttributes()` has been changed to not trigger events and not respect optimistic locking anymore to
differentiate it more from calling `update(false)` and to ensure it can be used in `afterSave()` without triggering infinite
loops.
...@@ -8,9 +8,7 @@ ...@@ -8,9 +8,7 @@
namespace yii\base; namespace yii\base;
/** /**
* ModelEvent class. * ModelEvent represents the parameter needed by [[Model]] events.
*
* ModelEvent represents the parameter needed by model events.
* *
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
......
...@@ -89,6 +89,7 @@ return [ ...@@ -89,6 +89,7 @@ return [
'yii\db\ActiveRecord' => YII_PATH . '/db/ActiveRecord.php', 'yii\db\ActiveRecord' => YII_PATH . '/db/ActiveRecord.php',
'yii\db\ActiveRecordInterface' => YII_PATH . '/db/ActiveRecordInterface.php', 'yii\db\ActiveRecordInterface' => YII_PATH . '/db/ActiveRecordInterface.php',
'yii\db\ActiveRelationTrait' => YII_PATH . '/db/ActiveRelationTrait.php', 'yii\db\ActiveRelationTrait' => YII_PATH . '/db/ActiveRelationTrait.php',
'yii\db\AfterSaveEvent' => YII_PATH . '/db/AfterSaveEvent.php',
'yii\db\BaseActiveRecord' => YII_PATH . '/db/BaseActiveRecord.php', 'yii\db\BaseActiveRecord' => YII_PATH . '/db/BaseActiveRecord.php',
'yii\db\BatchQueryResult' => YII_PATH . '/db/BatchQueryResult.php', 'yii\db\BatchQueryResult' => YII_PATH . '/db/BatchQueryResult.php',
'yii\db\ColumnSchema' => YII_PATH . '/db/ColumnSchema.php', 'yii\db\ColumnSchema' => YII_PATH . '/db/ColumnSchema.php',
......
...@@ -431,8 +431,8 @@ class ActiveRecord extends BaseActiveRecord ...@@ -431,8 +431,8 @@ class ActiveRecord extends BaseActiveRecord
} }
} }
$this->afterSave(true);
$this->setOldAttributes($values); $this->setOldAttributes($values);
$this->afterSave(true, $values);
return true; return true;
} }
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
use yii\base\Event;
/**
* AfterSaveEvent represents the information available in [[ActiveRecord::EVENT_AFTER_INSERT]] and [[ActiveRecord::EVENT_AFTER_UPDATE]].
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class AfterSaveEvent extends Event
{
/**
* @var array The attribute values that had changed and were saved.
*/
public $changedAttributes;
}
...@@ -644,16 +644,16 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface ...@@ -644,16 +644,16 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
* Updates the specified attributes. * Updates the specified attributes.
* *
* This method is a shortcut to [[update()]] when data validation is not needed * This method is a shortcut to [[update()]] when data validation is not needed
* and only a list of attributes need to be updated. * and only a small set attributes need to be updated.
* *
* You may specify the attributes to be updated as name list or name-value pairs. * You may specify the attributes to be updated as name list or name-value pairs.
* If the latter, the corresponding attribute values will be modified accordingly. * If the latter, the corresponding attribute values will be modified accordingly.
* The method will then save the specified attributes into database. * The method will then save the specified attributes into database.
* *
* Note that this method will NOT perform data validation. * Note that this method will **not** perform data validation and will **not** trigger events.
* *
* @param array $attributes the attributes (names or name-value pairs) to be updated * @param array $attributes the attributes (names or name-value pairs) to be updated
* @return integer|boolean the number of rows affected, or false if [[beforeSave()]] stops the updating process. * @return integer the number of rows affected.
*/ */
public function updateAttributes($attributes) public function updateAttributes($attributes)
{ {
...@@ -666,7 +666,19 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface ...@@ -666,7 +666,19 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
$attrs[] = $name; $attrs[] = $name;
} }
} }
return $this->updateInternal($attrs);
$values = $this->getDirtyAttributes($attrs);
if (empty($values)) {
return 0;
}
$rows = $this->updateAll($values, $this->getOldPrimaryKey(true));
foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $this->_attributes[$name];
}
return $rows;
} }
/** /**
...@@ -680,7 +692,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface ...@@ -680,7 +692,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
} }
$values = $this->getDirtyAttributes($attributes); $values = $this->getDirtyAttributes($attributes);
if (empty($values)) { if (empty($values)) {
$this->afterSave(false); $this->afterSave(false, $values);
return 0; return 0;
} }
$condition = $this->getOldPrimaryKey(true); $condition = $this->getOldPrimaryKey(true);
...@@ -699,10 +711,10 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface ...@@ -699,10 +711,10 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
throw new StaleObjectException('The object being updated is outdated.'); throw new StaleObjectException('The object being updated is outdated.');
} }
$this->afterSave(false);
foreach ($values as $name => $value) { foreach ($values as $name => $value) {
$this->_oldAttributes[$name] = $this->_attributes[$name]; $this->_oldAttributes[$name] = $this->_attributes[$name];
} }
$this->afterSave(false, $values);
return $rows; return $rows;
} }
...@@ -855,15 +867,18 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface ...@@ -855,15 +867,18 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
/** /**
* This method is called at the end of inserting or updating a record. * This method is called at the end of inserting or updating a record.
* The default implementation will trigger an [[EVENT_AFTER_INSERT]] event when `$insert` is true, * The default implementation will trigger an [[EVENT_AFTER_INSERT]] event when `$insert` is true,
* or an [[EVENT_AFTER_UPDATE]] event if `$insert` is false. * or an [[EVENT_AFTER_UPDATE]] event if `$insert` is false. The event class used is [[AfterSaveEvent]].
* When overriding this method, make sure you call the parent implementation so that * When overriding this method, make sure you call the parent implementation so that
* the event is triggered. * the event is triggered.
* @param boolean $insert whether this method called while inserting a record. * @param boolean $insert whether this method called while inserting a record.
* If false, it means the method is called while updating a record. * If false, it means the method is called while updating a record.
* @param array $changedAttributes The attribute values that had changed and were saved.
*/ */
public function afterSave($insert) public function afterSave($insert, $changedAttributes)
{ {
$this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE); $this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE, new AfterSaveEvent([
'changedAttributes' => $changedAttributes
]));
} }
/** /**
......
...@@ -58,11 +58,11 @@ class Customer extends ActiveRecord ...@@ -58,11 +58,11 @@ class Customer extends ActiveRecord
})->orderBy('id'); })->orderBy('id');
} }
public function afterSave($insert) public function afterSave($insert, $changedAttributes)
{ {
ActiveRecordTest::$afterSaveInsert = $insert; ActiveRecordTest::$afterSaveInsert = $insert;
ActiveRecordTest::$afterSaveNewRecord = $this->isNewRecord; ActiveRecordTest::$afterSaveNewRecord = $this->isNewRecord;
parent::afterSave($insert); parent::afterSave($insert, $changedAttributes);
} }
/** /**
......
...@@ -40,11 +40,11 @@ class Customer extends ActiveRecord ...@@ -40,11 +40,11 @@ class Customer extends ActiveRecord
return $this->hasMany(OrderWithNullFK::className(), ['customer_id' => 'id'])->orderBy('created_at'); return $this->hasMany(OrderWithNullFK::className(), ['customer_id' => 'id'])->orderBy('created_at');
} }
public function afterSave($insert) public function afterSave($insert, $changedAttributes)
{ {
ActiveRecordTest::$afterSaveInsert = $insert; ActiveRecordTest::$afterSaveInsert = $insert;
ActiveRecordTest::$afterSaveNewRecord = $this->isNewRecord; ActiveRecordTest::$afterSaveNewRecord = $this->isNewRecord;
parent::afterSave($insert); parent::afterSave($insert, $changedAttributes);
} }
/** /**
......
...@@ -38,11 +38,11 @@ class Customer extends ActiveRecord ...@@ -38,11 +38,11 @@ class Customer extends ActiveRecord
/** /**
* @inheritdoc * @inheritdoc
*/ */
public function afterSave($insert) public function afterSave($insert, $changedAttributes)
{ {
ActiveRecordTest::$afterSaveInsert = $insert; ActiveRecordTest::$afterSaveInsert = $insert;
ActiveRecordTest::$afterSaveNewRecord = $this->isNewRecord; ActiveRecordTest::$afterSaveNewRecord = $this->isNewRecord;
parent::afterSave($insert); parent::afterSave($insert, $changedAttributes);
} }
/** /**
......
...@@ -816,7 +816,7 @@ trait ActiveRecordTestTrait ...@@ -816,7 +816,7 @@ trait ActiveRecordTestTrait
$this->afterSave(); $this->afterSave();
$this->assertNotNull($customer->id); $this->assertNotNull($customer->id);
$this->assertTrue(static::$afterSaveNewRecord); $this->assertFalse(static::$afterSaveNewRecord);
$this->assertTrue(static::$afterSaveInsert); $this->assertTrue(static::$afterSaveInsert);
$this->assertFalse($customer->isNewRecord); $this->assertFalse($customer->isNewRecord);
} }
...@@ -867,7 +867,7 @@ trait ActiveRecordTestTrait ...@@ -867,7 +867,7 @@ trait ActiveRecordTestTrait
/* @var $customerClass \yii\db\ActiveRecordInterface */ /* @var $customerClass \yii\db\ActiveRecordInterface */
$customerClass = $this->getCustomerClass(); $customerClass = $this->getCustomerClass();
/* @var $this TestCase|ActiveRecordTestTrait */ /* @var $this TestCase|ActiveRecordTestTrait */
// save /* @var $customer Customer */
$customer = $customerClass::findOne(2); $customer = $customerClass::findOne(2);
$this->assertTrue($customer instanceof $customerClass); $this->assertTrue($customer instanceof $customerClass);
$this->assertEquals('user2', $customer->name); $this->assertEquals('user2', $customer->name);
...@@ -879,8 +879,8 @@ trait ActiveRecordTestTrait ...@@ -879,8 +879,8 @@ trait ActiveRecordTestTrait
$this->afterSave(); $this->afterSave();
$this->assertEquals('user2x', $customer->name); $this->assertEquals('user2x', $customer->name);
$this->assertFalse($customer->isNewRecord); $this->assertFalse($customer->isNewRecord);
$this->assertFalse(static::$afterSaveNewRecord); $this->assertNull(static::$afterSaveNewRecord);
$this->assertFalse(static::$afterSaveInsert); $this->assertNull(static::$afterSaveInsert);
$customer2 = $customerClass::findOne(2); $customer2 = $customerClass::findOne(2);
$this->assertEquals('user2x', $customer2->name); $this->assertEquals('user2x', $customer2->name);
......
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