mirror of
https://github.com/kamilwylegala/cakephp2-php8.git
synced 2024-11-15 03:18:26 +00:00
Fix transactions do not get rollbacked in saveAssociated/saveMany
Refs #2849
This commit is contained in:
parent
3d77ce5d34
commit
799500ce6d
2 changed files with 253 additions and 137 deletions
|
@ -2215,6 +2215,7 @@ class Model extends Object implements CakeEventListener {
|
||||||
* @return mixed If atomic: True on success, or false on failure.
|
* @return mixed If atomic: True on success, or false on failure.
|
||||||
* Otherwise: array similar to the $data array passed, but values are set to true/false
|
* Otherwise: array similar to the $data array passed, but values are set to true/false
|
||||||
* depending on whether each record saved successfully.
|
* depending on whether each record saved successfully.
|
||||||
|
* @throws PDOException
|
||||||
* @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-savemany-array-data-null-array-options-array
|
* @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-savemany-array-data-null-array-options-array
|
||||||
*/
|
*/
|
||||||
public function saveMany($data = null, $options = array()) {
|
public function saveMany($data = null, $options = array()) {
|
||||||
|
@ -2245,47 +2246,58 @@ class Model extends Object implements CakeEventListener {
|
||||||
if ($options['atomic']) {
|
if ($options['atomic']) {
|
||||||
$db = $this->getDataSource();
|
$db = $this->getDataSource();
|
||||||
$transactionBegun = $db->begin();
|
$transactionBegun = $db->begin();
|
||||||
|
} else {
|
||||||
|
$transactionBegun = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$return = array();
|
try {
|
||||||
foreach ($data as $key => $record) {
|
$return = array();
|
||||||
$validates = $this->create(null) !== null;
|
foreach ($data as $key => $record) {
|
||||||
$saved = false;
|
$validates = $this->create(null) !== null;
|
||||||
if ($validates) {
|
$saved = false;
|
||||||
if ($options['deep']) {
|
if ($validates) {
|
||||||
$saved = $this->saveAssociated($record, array_merge($options, array('atomic' => false)));
|
if ($options['deep']) {
|
||||||
} else {
|
$saved = $this->saveAssociated($record, array_merge($options, array('atomic' => false)));
|
||||||
$saved = $this->save($record, $options);
|
} else {
|
||||||
|
$saved = $this->save($record, $options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$validates = ($validates && ($saved === true || (is_array($saved) && !in_array(false, $saved, true))));
|
||||||
|
if (!$validates) {
|
||||||
|
$validationErrors[$key] = $this->validationErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$options['atomic']) {
|
||||||
|
$return[$key] = $validates;
|
||||||
|
} elseif (!$validates) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$validates = ($validates && ($saved === true || (is_array($saved) && !in_array(false, $saved, true))));
|
$this->validationErrors = $validationErrors;
|
||||||
if (!$validates) {
|
|
||||||
$validationErrors[$key] = $this->validationErrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$options['atomic']) {
|
if (!$options['atomic']) {
|
||||||
$return[$key] = $validates;
|
return $return;
|
||||||
} elseif (!$validates) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$this->validationErrors = $validationErrors;
|
if ($validates) {
|
||||||
|
if ($transactionBegun) {
|
||||||
|
return $db->commit() !== false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!$options['atomic']) {
|
|
||||||
return $return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($validates) {
|
|
||||||
if ($transactionBegun) {
|
if ($transactionBegun) {
|
||||||
return $db->commit() !== false;
|
$db->rollback();
|
||||||
}
|
}
|
||||||
return true;
|
return false;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if ($transactionBegun) {
|
||||||
|
$db->rollback();
|
||||||
|
}
|
||||||
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
$db->rollback();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2337,6 +2349,7 @@ class Model extends Object implements CakeEventListener {
|
||||||
* @return mixed If atomic: True on success, or false on failure.
|
* @return mixed If atomic: True on success, or false on failure.
|
||||||
* Otherwise: array similar to the $data array passed, but values are set to true/false
|
* Otherwise: array similar to the $data array passed, but values are set to true/false
|
||||||
* depending on whether each record saved successfully.
|
* depending on whether each record saved successfully.
|
||||||
|
* @throws PDOException
|
||||||
* @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-saveassociated-array-data-null-array-options-array
|
* @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-saveassociated-array-data-null-array-options-array
|
||||||
*/
|
*/
|
||||||
public function saveAssociated($data = null, $options = array()) {
|
public function saveAssociated($data = null, $options = array()) {
|
||||||
|
@ -2368,133 +2381,144 @@ class Model extends Object implements CakeEventListener {
|
||||||
if ($options['atomic']) {
|
if ($options['atomic']) {
|
||||||
$db = $this->getDataSource();
|
$db = $this->getDataSource();
|
||||||
$transactionBegun = $db->begin();
|
$transactionBegun = $db->begin();
|
||||||
|
} else {
|
||||||
|
$transactionBegun = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$associations = $this->getAssociated();
|
try {
|
||||||
$return = array();
|
$associations = $this->getAssociated();
|
||||||
$validates = true;
|
$return = array();
|
||||||
foreach ($data as $association => $values) {
|
$validates = true;
|
||||||
$isEmpty = empty($values) || (isset($values[$association]) && empty($values[$association]));
|
foreach ($data as $association => $values) {
|
||||||
if ($isEmpty || !isset($associations[$association]) || $associations[$association] !== 'belongsTo') {
|
$isEmpty = empty($values) || (isset($values[$association]) && empty($values[$association]));
|
||||||
continue;
|
if ($isEmpty || !isset($associations[$association]) || $associations[$association] !== 'belongsTo') {
|
||||||
}
|
continue;
|
||||||
|
|
||||||
$Model = $this->{$association};
|
|
||||||
|
|
||||||
$validates = $Model->create(null) !== null;
|
|
||||||
$saved = false;
|
|
||||||
if ($validates) {
|
|
||||||
if ($options['deep']) {
|
|
||||||
$saved = $Model->saveAssociated($values, array('atomic' => false) + $options);
|
|
||||||
} else {
|
|
||||||
$saved = $Model->save($values, array('atomic' => false) + $options);
|
|
||||||
}
|
}
|
||||||
$validates = ($saved === true || (is_array($saved) && !in_array(false, $saved, true)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($validates) {
|
$Model = $this->{$association};
|
||||||
$key = $this->belongsTo[$association]['foreignKey'];
|
|
||||||
if (isset($data[$this->alias])) {
|
|
||||||
$data[$this->alias][$key] = $Model->id;
|
|
||||||
} else {
|
|
||||||
$data = array_merge(array($key => $Model->id), $data, array($key => $Model->id));
|
|
||||||
}
|
|
||||||
$options = $this->_addToWhiteList($key, $options);
|
|
||||||
} else {
|
|
||||||
$validationErrors[$association] = $Model->validationErrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
$return[$association] = $validates;
|
$validates = $Model->create(null) !== null;
|
||||||
}
|
$saved = false;
|
||||||
|
if ($validates) {
|
||||||
if ($validates && !($this->create(null) !== null && $this->save($data, $options))) {
|
if ($options['deep']) {
|
||||||
$validationErrors[$this->alias] = $this->validationErrors;
|
$saved = $Model->saveAssociated($values, array('atomic' => false) + $options);
|
||||||
$validates = false;
|
|
||||||
}
|
|
||||||
$return[$this->alias] = $validates;
|
|
||||||
|
|
||||||
foreach ($data as $association => $values) {
|
|
||||||
if (!$validates) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$isEmpty = empty($values) || (isset($values[$association]) && empty($values[$association]));
|
|
||||||
if ($isEmpty || !isset($associations[$association])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$Model = $this->{$association};
|
|
||||||
|
|
||||||
$type = $associations[$association];
|
|
||||||
$key = $this->{$type}[$association]['foreignKey'];
|
|
||||||
switch ($type) {
|
|
||||||
case 'hasOne':
|
|
||||||
if (isset($values[$association])) {
|
|
||||||
$values[$association][$key] = $this->id;
|
|
||||||
} else {
|
} else {
|
||||||
$values = array_merge(array($key => $this->id), $values, array($key => $this->id));
|
$saved = $Model->save($values, array('atomic' => false) + $options);
|
||||||
}
|
}
|
||||||
|
$validates = ($saved === true || (is_array($saved) && !in_array(false, $saved, true)));
|
||||||
|
}
|
||||||
|
|
||||||
$validates = $Model->create(null) !== null;
|
if ($validates) {
|
||||||
$saved = false;
|
$key = $this->belongsTo[$association]['foreignKey'];
|
||||||
|
if (isset($data[$this->alias])) {
|
||||||
|
$data[$this->alias][$key] = $Model->id;
|
||||||
|
} else {
|
||||||
|
$data = array_merge(array($key => $Model->id), $data, array($key => $Model->id));
|
||||||
|
}
|
||||||
|
$options = $this->_addToWhiteList($key, $options);
|
||||||
|
} else {
|
||||||
|
$validationErrors[$association] = $Model->validationErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
$return[$association] = $validates;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($validates && !($this->create(null) !== null && $this->save($data, $options))) {
|
||||||
|
$validationErrors[$this->alias] = $this->validationErrors;
|
||||||
|
$validates = false;
|
||||||
|
}
|
||||||
|
$return[$this->alias] = $validates;
|
||||||
|
|
||||||
|
foreach ($data as $association => $values) {
|
||||||
|
if (!$validates) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$isEmpty = empty($values) || (isset($values[$association]) && empty($values[$association]));
|
||||||
|
if ($isEmpty || !isset($associations[$association])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$Model = $this->{$association};
|
||||||
|
|
||||||
|
$type = $associations[$association];
|
||||||
|
$key = $this->{$type}[$association]['foreignKey'];
|
||||||
|
switch ($type) {
|
||||||
|
case 'hasOne':
|
||||||
|
if (isset($values[$association])) {
|
||||||
|
$values[$association][$key] = $this->id;
|
||||||
|
} else {
|
||||||
|
$values = array_merge(array($key => $this->id), $values, array($key => $this->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
$validates = $Model->create(null) !== null;
|
||||||
|
$saved = false;
|
||||||
|
|
||||||
|
if ($validates) {
|
||||||
|
$options = $Model->_addToWhiteList($key, $options);
|
||||||
|
if ($options['deep']) {
|
||||||
|
$saved = $Model->saveAssociated($values, array('atomic' => false) + $options);
|
||||||
|
} else {
|
||||||
|
$saved = $Model->save($values, $options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$validates = ($validates && ($saved === true || (is_array($saved) && !in_array(false, $saved, true))));
|
||||||
|
if (!$validates) {
|
||||||
|
$validationErrors[$association] = $Model->validationErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
$return[$association] = $validates;
|
||||||
|
break;
|
||||||
|
case 'hasMany':
|
||||||
|
foreach ($values as $i => $value) {
|
||||||
|
if (isset($values[$i][$association])) {
|
||||||
|
$values[$i][$association][$key] = $this->id;
|
||||||
|
} else {
|
||||||
|
$values[$i] = array_merge(array($key => $this->id), $value, array($key => $this->id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($validates) {
|
|
||||||
$options = $Model->_addToWhiteList($key, $options);
|
$options = $Model->_addToWhiteList($key, $options);
|
||||||
if ($options['deep']) {
|
$_return = $Model->saveMany($values, array('atomic' => false) + $options);
|
||||||
$saved = $Model->saveAssociated($values, array('atomic' => false) + $options);
|
if (in_array(false, $_return, true)) {
|
||||||
} else {
|
$validationErrors[$association] = $Model->validationErrors;
|
||||||
$saved = $Model->save($values, $options);
|
$validates = false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$validates = ($validates && ($saved === true || (is_array($saved) && !in_array(false, $saved, true))));
|
$return[$association] = $_return;
|
||||||
if (!$validates) {
|
break;
|
||||||
$validationErrors[$association] = $Model->validationErrors;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$return[$association] = $validates;
|
|
||||||
break;
|
|
||||||
case 'hasMany':
|
|
||||||
foreach ($values as $i => $value) {
|
|
||||||
if (isset($values[$i][$association])) {
|
|
||||||
$values[$i][$association][$key] = $this->id;
|
|
||||||
} else {
|
|
||||||
$values[$i] = array_merge(array($key => $this->id), $value, array($key => $this->id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$options = $Model->_addToWhiteList($key, $options);
|
|
||||||
$_return = $Model->saveMany($values, array('atomic' => false) + $options);
|
|
||||||
if (in_array(false, $_return, true)) {
|
|
||||||
$validationErrors[$association] = $Model->validationErrors;
|
|
||||||
$validates = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$return[$association] = $_return;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
$this->validationErrors = $validationErrors;
|
||||||
$this->validationErrors = $validationErrors;
|
|
||||||
|
|
||||||
if (isset($validationErrors[$this->alias])) {
|
if (isset($validationErrors[$this->alias])) {
|
||||||
$this->validationErrors = $validationErrors[$this->alias];
|
$this->validationErrors = $validationErrors[$this->alias];
|
||||||
unset($validationErrors[$this->alias]);
|
unset($validationErrors[$this->alias]);
|
||||||
$this->validationErrors = array_merge($this->validationErrors, $validationErrors);
|
$this->validationErrors = array_merge($this->validationErrors, $validationErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$options['atomic']) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
if ($validates) {
|
||||||
|
if ($transactionBegun) {
|
||||||
|
return $db->commit() !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!$options['atomic']) {
|
|
||||||
return $return;
|
|
||||||
}
|
|
||||||
if ($validates) {
|
|
||||||
if ($transactionBegun) {
|
if ($transactionBegun) {
|
||||||
return $db->commit() !== false;
|
$db->rollback();
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
return true;
|
} catch (Exception $e) {
|
||||||
|
if ($transactionBegun) {
|
||||||
|
$db->rollback();
|
||||||
|
}
|
||||||
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
$db->rollback();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -4143,6 +4143,25 @@ class ModelWriteTest extends BaseModelTest {
|
||||||
);
|
);
|
||||||
$Post->saveAll($data, array('atomic' => true, 'validate' => true));
|
$Post->saveAll($data, array('atomic' => true, 'validate' => true));
|
||||||
|
|
||||||
|
// If exception thrown, rollback() should be called too.
|
||||||
|
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
|
||||||
|
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
|
||||||
|
$db->expects($this->never())->method('commit');
|
||||||
|
$db->expects($this->once())->method('rollback');
|
||||||
|
|
||||||
|
$Post->setDataSourceObject($db);
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
array('author_id' => 1, 'title' => 'New Fourth Post'),
|
||||||
|
array('author_id' => 1, 'title' => 'New Fifth Post', 'body' => $db->expression('PDO_EXCEPTION()'))
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$Post->saveAll($data, array('atomic' => true, 'validate' => true));
|
||||||
|
$this->fail('No exception thrown');
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise, commit() should be called.
|
// Otherwise, commit() should be called.
|
||||||
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
|
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
|
||||||
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
|
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
|
||||||
|
@ -4193,6 +4212,33 @@ class ModelWriteTest extends BaseModelTest {
|
||||||
);
|
);
|
||||||
$Post->saveAll($data, array('validate' => true));
|
$Post->saveAll($data, array('validate' => true));
|
||||||
|
|
||||||
|
// If exception thrown, rollback() should be called too.
|
||||||
|
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
|
||||||
|
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
|
||||||
|
$db->expects($this->never())->method('commit');
|
||||||
|
$db->expects($this->once())->method('rollback');
|
||||||
|
|
||||||
|
$Post->setDataSourceObject($db);
|
||||||
|
$Post->Author->setDataSourceObject($db);
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
'Post' => array(
|
||||||
|
'title' => 'New post',
|
||||||
|
'body' => $db->expression('PDO_EXCEPTION()'),
|
||||||
|
'published' => 'Y'
|
||||||
|
),
|
||||||
|
'Author' => array(
|
||||||
|
'user' => 'New user',
|
||||||
|
'password' => "sekret"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$Post->saveAll($data, array('validate' => true));
|
||||||
|
$this->fail('No exception thrown');
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise, commit() should be called.
|
// Otherwise, commit() should be called.
|
||||||
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
|
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
|
||||||
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
|
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
|
||||||
|
@ -5605,6 +5651,25 @@ class ModelWriteTest extends BaseModelTest {
|
||||||
);
|
);
|
||||||
$Post->saveMany($data, array('validate' => true));
|
$Post->saveMany($data, array('validate' => true));
|
||||||
|
|
||||||
|
// If exception thrown, rollback() should be called too.
|
||||||
|
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
|
||||||
|
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
|
||||||
|
$db->expects($this->never())->method('commit');
|
||||||
|
$db->expects($this->once())->method('rollback');
|
||||||
|
|
||||||
|
$Post->setDataSourceObject($db);
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
array('author_id' => 1, 'title' => 'New Fourth Post'),
|
||||||
|
array('author_id' => 1, 'title' => 'New Fifth Post', 'body' => $db->expression('PDO_EXCEPTION()'))
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$Post->saveMany($data, array('validate' => true));
|
||||||
|
$this->fail('No exception thrown');
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise, commit() should be called.
|
// Otherwise, commit() should be called.
|
||||||
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
|
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
|
||||||
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
|
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
|
||||||
|
@ -5655,6 +5720,33 @@ class ModelWriteTest extends BaseModelTest {
|
||||||
);
|
);
|
||||||
$Post->saveAssociated($data, array('validate' => true, 'atomic' => true));
|
$Post->saveAssociated($data, array('validate' => true, 'atomic' => true));
|
||||||
|
|
||||||
|
// If exception thrown, commit() should be called.
|
||||||
|
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
|
||||||
|
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
|
||||||
|
$db->expects($this->never())->method('commit');
|
||||||
|
$db->expects($this->once())->method('rollback');
|
||||||
|
|
||||||
|
$Post->setDataSourceObject($db);
|
||||||
|
$Post->Author->setDataSourceObject($db);
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
'Post' => array(
|
||||||
|
'title' => 'New post',
|
||||||
|
'body' => $db->expression('PDO_EXCEPTION()'),
|
||||||
|
'published' => 'Y'
|
||||||
|
),
|
||||||
|
'Author' => array(
|
||||||
|
'user' => 'New user',
|
||||||
|
'password' => "sekret"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$Post->saveAssociated($data, array('validate' => true, 'atomic' => true));
|
||||||
|
$this->fail('No exception thrown');
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise, commit() should be called.
|
// Otherwise, commit() should be called.
|
||||||
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
|
$db = $this->_getMockDboSource(array('begin', 'commit', 'rollback'));
|
||||||
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
|
$db->expects($this->once())->method('begin')->will($this->returnValue(true));
|
||||||
|
|
Loading…
Reference in a new issue