mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #5223 from tractorcow/pulls/4.0/transactions
API Add SS_Database::withTransaction for nice enclosed transactions
This commit is contained in:
commit
700cf9bd56
@ -489,6 +489,42 @@ abstract class SS_Database {
|
||||
*/
|
||||
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
|
||||
*
|
||||
|
@ -151,6 +151,44 @@ class DatabaseTest extends SapphireTest {
|
||||
$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 {
|
||||
|
Loading…
Reference in New Issue
Block a user