Commit e5195937 by Ragazzo Committed by Alexander Makarov

Fixes #3384: Added callback-style transactions

parent 9c2e7661
...@@ -99,6 +99,7 @@ Yii Framework 2 Change Log ...@@ -99,6 +99,7 @@ Yii Framework 2 Change Log
- Enh #3298: Supported configuring `View::theme` using a class name (netyum, qiangxue) - Enh #3298: Supported configuring `View::theme` using a class name (netyum, qiangxue)
- Enh #3328: `BaseMailer` generates better text body from html body (armab) - Enh #3328: `BaseMailer` generates better text body from html body (armab)
- Enh #3380: Allow `value` in `defaultValueValidator` to be a closure (Alex-Code) - Enh #3380: Allow `value` in `defaultValueValidator` to be a closure (Alex-Code)
- Enh #3384: Added callback-style transactions (leandrogehlen, Ragazzo, samdark)
- Enh #3399, #3241: Added support for MS SQL Server older than 2012 (fourteenmeister, samdark) - Enh #3399, #3241: Added support for MS SQL Server older than 2012 (fourteenmeister, samdark)
- Enh #3472: Added configurable option to encode spaces in dropDownLists and listBoxes (kartik-v) - Enh #3472: Added configurable option to encode spaces in dropDownLists and listBoxes (kartik-v)
- Enh #3518: `yii\helpers\Html::encode()` now replaces invalid code sequences with "�" (DaSourcerer) - Enh #3518: `yii\helpers\Html::encode()` now replaces invalid code sequences with "�" (DaSourcerer)
......
...@@ -71,7 +71,27 @@ use yii\caching\Cache; ...@@ -71,7 +71,27 @@ use yii\caching\Cache;
* $transaction->rollBack(); * $transaction->rollBack();
* } * }
* ~~~ * ~~~
* *
* You also can use shortcut for the above like the following:
*
* ~~~
* $connection->transaction(function() {
* $order = new Order($customer);
* $order->save();
* $order->addItems($items);
* });
* ~~~
*
* If needed you can pass transaction object instance as a second parameter, for example when you need to
* set custom transaction isolation level:
*
* ~~~
* $connection->transaction(function() {
*
* // your code here
* }, $connection->beginTransaction(Transaction::READ_UNCOMMITTED));
* ~~~
*
* Connection is often used as an application component and configured in the application * Connection is often used as an application component and configured in the application
* configuration like the following: * configuration like the following:
* *
...@@ -439,6 +459,30 @@ class Connection extends Component ...@@ -439,6 +459,30 @@ class Connection extends Component
} }
/** /**
* Executes callback provided in a transaction.
*
* @param \Closure $callback
* @param string|null $isolationLevel The isolation level to use for this transaction.
* See [[Transaction::begin()]] for details.
* @throws \Exception
* @return mixed result of callback function
*/
public function transaction(\Closure $callback, $isolationLevel = null)
{
$transaction = $this->beginTransaction($isolationLevel);
try {
$result = $callback();
$transaction->commit();
} catch (\Exception $e) {
$transaction->rollBack();
throw $e;
}
return $result;
}
/**
* Returns the schema information for the database opened by this connection. * Returns the schema information for the database opened by this connection.
* @return Schema the schema information for the database opened by this connection. * @return Schema the schema information for the database opened by this connection.
* @throws NotSupportedException if there is no support for the current driver type * @throws NotSupportedException if there is no support for the current driver type
......
...@@ -121,4 +121,49 @@ class ConnectionTest extends DatabaseTestCase ...@@ -121,4 +121,49 @@ class ConnectionTest extends DatabaseTestCase
$transaction->commit(); $transaction->commit();
} }
/**
* @expectedException \Exception
*/
public function testTransactionShortcutException()
{
$connection = $this->getConnection(true);
$connection->transaction(function () use ($connection) {
$connection->createCommand()->insert('profile', ['description' => 'test transaction shortcut'])->execute();
throw new \Exception('Exception in transaction shortcut');
});
$profilesCount = $connection->createCommand("SELECT COUNT(*) FROM profile WHERE description = 'test transaction shortcut';")->queryScalar();
$this->assertEquals(0, $profilesCount, 'profile should not be inserted in transaction shortcut');
}
public function testTransactionShortcutCorrect()
{
$connection = $this->getConnection(true);
$result = $connection->transaction(function () use ($connection) {
$connection->createCommand()->insert('profile', ['description' => 'test transaction shortcut'])->execute();
return true;
});
$this->assertTrue($result, 'transaction shortcut valid value should be returned from callback');
$profilesCount = $connection->createCommand("SELECT COUNT(*) FROM profile WHERE description = 'test transaction shortcut';")->queryScalar();
$this->assertEquals(1, $profilesCount, 'profile should be inserted in transaction shortcut');
}
public function testTransactionShortcutCustom()
{
$connection = $this->getConnection(true);
$result = $connection->transaction(function () use ($connection) {
$connection->createCommand()->insert('profile', ['description' => 'test transaction shortcut'])->execute();
return true;
}, $connection->beginTransaction(Transaction::READ_UNCOMMITTED));
$this->assertTrue($result, 'transaction shortcut valid value should be returned from callback');
$profilesCount = $connection->createCommand("SELECT COUNT(*) FROM profile WHERE description = 'test transaction shortcut';")->queryScalar();
$this->assertEquals(1, $profilesCount, 'profile should be inserted in transaction shortcut');
}
} }
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