API Add SS_Database::withTransaction for nice enclosed transactions

This commit is contained in:
Damian Mooyman 2016-03-23 13:42:51 +13:00
parent 633eb0163e
commit 8abede1339
2 changed files with 74 additions and 0 deletions

View File

@ -489,6 +489,42 @@ abstract class SS_Database {
*/ */
abstract public function supportsTransactions(); abstract public function supportsTransactions();
/**
* Invoke $callback within a transaction
*
* @param callable $callback Callback to run
* @param callable $errorCallback Optional callback to run after rolling back transaction.
* @param bool|string $transactionMode Optional transaction mode to use
* @param bool $errorIfTransactionsUnsupported If true, this method will fail if transactions are unsupported.
* Otherwise, the $callback will potentially be invoked outside of a transaction.
* @throws Exception
*/
public function withTransaction(
$callback, $errorCallback = null, $transactionMode = false, $errorIfTransactionsUnsupported = false
) {
$supported = $this->supportsTransactions();
if(!$supported && $errorIfTransactionsUnsupported) {
throw new BadMethodCallException("Transactions not supported by this database.");
}
if($supported) {
$this->transactionStart($transactionMode);
}
try {
call_user_func($callback);
} catch (Exception $ex) {
if($supported) {
$this->transactionRollback();
}
if($errorCallback) {
call_user_func($errorCallback);
}
throw $ex;
}
if($supported) {
$this->transactionEnd();
}
}
/* /*
* Determines if the current database connection supports a given list of extensions * Determines if the current database connection supports a given list of extensions
* *

View File

@ -151,6 +151,44 @@ class DatabaseTest extends SapphireTest {
$this->assertTrue($db->canLock('DatabaseTest'), 'Can lock again after releasing it'); $this->assertTrue($db->canLock('DatabaseTest'), 'Can lock again after releasing it');
} }
public function testTransactions() {
$conn = DB::getConn();
if(!$conn->supportsTransactions()) {
$this->markTestSkipped("DB Doesn't support transactions");
return;
}
// Test that successful transactions are comitted
$obj = new DatabaseTest_MyObject();
$failed = false;
$conn->withTransaction(function() use (&$obj) {
$obj->MyField = 'Save 1';
$obj->write();
}, function() use (&$failed) {
$failed = true;
});
$this->assertEquals('Save 1', DatabaseTest_MyObject::get()->first()->MyField);
$this->assertFalse($failed);
// Test failed transactions are rolled back
$ex = null;
$failed = false;
try {
$conn->withTransaction(function() use (&$obj) {
$obj->MyField = 'Save 2';
$obj->write();
throw new Exception("error");
}, function() use (&$failed) {
$failed = true;
});
} catch ( Exception $ex) {}
$this->assertTrue($failed);
$this->assertEquals('Save 1', DatabaseTest_MyObject::get()->first()->MyField);
$this->assertInstanceOf('Exception', $ex);
$this->assertEquals('error', $ex->getMessage());
}
} }
class DatabaseTest_MyObject extends DataObject implements TestOnly { class DatabaseTest_MyObject extends DataObject implements TestOnly {