Skip to content
Permalink
Browse files

Throwing an exception when the afterSave aborts the transaction

Fixes #9079
  • Loading branch information...
lorenzo committed Jul 9, 2016
1 parent 49540e0 commit f38839f1b3e5bff3203cfe82bda292e96cef6cdc
Showing with 135 additions and 18 deletions.
  1. +26 −0 src/ORM/Exception/RolledbackTransactionException.php
  2. +41 −18 src/ORM/Table.php
  3. +68 −0 tests/TestCase/ORM/TableRegressionTest.php
@@ -0,0 +1,26 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @since 3.2.13
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\ORM\Exception;
use Cake\Core\Exception\Exception;
/**
* Used when a behavior cannot be found.
*
*/
class RolledbackTransactionException extends Exception
{
protected $_messageTemplate = 'The afterSave event in "%s" is aborting the transaction before the save process is done.';
}
@@ -33,6 +33,7 @@
use Cake\ORM\Association\HasMany;
use Cake\ORM\Association\HasOne;
use Cake\ORM\Exception\MissingEntityException;
use Cake\ORM\Exception\RolledbackTransactionException;
use Cake\ORM\Rule\IsUnique;
use Cake\Utility\Inflector;
use Cake\Validation\Validation;
@@ -1437,6 +1438,7 @@ public function exists($conditions)
* $articles->save($entity, ['associated' => false]);
* ```
*
* @throws RolledbackTransactionException if the transaction is aborted in the afterSave event
*/
public function save(EntityInterface $entity, $options = [])
{
@@ -1487,6 +1489,7 @@ public function save(EntityInterface $entity, $options = [])
* @param \ArrayObject $options the options to use for the save operation
* @return \Cake\Datasource\EntityInterface|bool
* @throws \RuntimeException When an entity is missing some of the primary keys.
* @throws RolledbackTransactionException if the transaction is aborted in the afterSave event
*/
protected function _processSave($entity, $options)
{
@@ -1534,32 +1537,52 @@ protected function _processSave($entity, $options)
}
if ($success) {
$success = $this->_associations->saveChildren(
$this,
$entity,
$options['associated'],
['_primary' => false] + $options->getArrayCopy()
);
if ($success || !$options['atomic']) {
$this->dispatchEvent('Model.afterSave', compact('entity', 'options'));
$entity->clean();
if (!$options['atomic'] && !$options['_primary']) {
$entity->isNew(false);
$entity->source($this->registryAlias());
}
$success = true;
}
$success = $this->_onSaveSuccess($entity, $options);
}
if (!$success && $isNew) {
$entity->unsetProperty($this->primaryKey());
$entity->isNew(true);
}
if ($success) {
return $entity;
return $success ? $entity : false;
}
/**
* Handles the saving of children associations and executing the afterSave logic
* once the entity for this table has been saved successfully.
*
* @param \Cake\Datasource\EntityInterface $entity the entity to be saved
* @param \ArrayObject $options the options to use for the save operation
* @return bool True on success
* @throws RolledbackTransactionException if the transaction is aborted in the afterSave event
*/
protected function _onSaveSuccess($entity, $options)
{
$success = $this->_associations->saveChildren(
$this,
$entity,
$options['associated'],
['_primary' => false] + $options->getArrayCopy()
);
if (!$success && $options['atomic']) {
return false;
}
$this->dispatchEvent('Model.afterSave', compact('entity', 'options'));
if ($options['atomic'] && !$this->connection()->inTransaction()) {
throw new RolledbackTransactionException(['table' => get_class($this)]);
}
$entity->clean();
if (!$options['atomic'] && !$options['_primary']) {
$entity->isNew(false);
$entity->source($this->registryAlias());
}
return false;
return true;
}
/**
@@ -0,0 +1,68 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since 3.2.13
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Test\TestCase\ORM;
use Cake\Core\Plugin;
use Cake\I18n\Time;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\TestCase;
/**
* Contains regression test for the Table class
*
*/
class TableRegressionTest extends TestCase
{
/**
* Fixture to be used
*
* @var array
*/
public $fixtures = [
'core.authors',
];
/**
* Tear down
*
* @return void
*/
public function tearDown()
{
parent::tearDown();
TableRegistry::clear();
}
/**
* Tests that an exception is thrown if the transaction is aborted
* in the afterSave callback
*
* @see https://github.com/cakephp/cakephp/issues/9079
* @expectedException \Cake\ORM\RolledbackTransactionException
* @return void
*/
public function testAfterSaveRollbackTransaction()
{
$table = TableRegistry::get('Authors');
$table->eventManager()->on('Model.afterSave', function () use ($table) {
$table->connection()->rollback();
});
$entity = $table->newEntity(['name' => 'Jon']);
$table->save($entity);
}
}

0 comments on commit f38839f

Please sign in to comment.
You can’t perform that action at this time.