Changes
Title
unchanged
How to nest DB transactions without actually nesting them.
Category
unchanged
How-tos
Yii version
unchanged
Tags
unchanged
transaction, transactions, db, begintransaction, rollback, commit
Content
changed
[...]
```php
class YActiveRecord extends CActiveRecord {
/**
* Keeps transaction status
* (true if previous transaction, transaction if
* local trasaction, null if no transaction).
*
* @var CDbTransaction
|| bool
|| null
*/
private $_transaction=null;
/**
* Begin a transaction on the database.
*/
public function beginTransaction() {
if(YII_DEBUG) {
Yii::trace("Begin transaction");
}
if($this->_transaction
= !==
null) {
throw new CException('Transaction already started');
}[...]
*/
public function commit() {
if($this->_transaction===null) {
throw new CException('No ongoing transaction');
} else {
$this->_transaction->commit();
}
$this->_YII_DEBUG) {
Yii::trace("Commit transaction
=null");
}
/**
* Rollback a transaction on the database.
* @throws CException
*/
public function rollback() { }
if($this->_transaction
===
null) {
throw new CException('No ongoing transaction');
} else
if($this->_transaction!==true){
$this->_transaction->
rollbackcommit();
}
$this->_transaction=null;
}
}
```
So to use the transaction replacements, you have to extend your models from YActiveRecord in stead of CActiveRecord. You can also choose to put the above code in your models of course.
As I found it cumbersome to do a 'getDbConnection()' first and then do the transaction request, I have made those "native" methods of the ActiveRecord. You are either doing a transaction with the model or not, you do not really want to know about the database.
The implemention will check if a transaction is already ongoing on the database or not. If there is one, no new transaction will be created and '$_transaction' is set to true to indicate that a transaction is active, but that it was not created in this instance.
It relies on the fact that you should begin and end a transaction in the same method. It does not allow you to end a transaction in another model (while other implementations allow you to do that, which might actually be error prone).
When ending the transaction ('commit' or 'rollback'), this implementation will check if this instance previously started a transaction, and then actually perform the 'commit' or 'rollback' depending on whether the real transaction was started locally or not.
The risk of this implementation is that a nested transaction that you 'rollback' is not actually rolled back in the database. This method relies on the fact that the calling method will check the result of the "sub"-method and rollback itself if an error is returned.
Hence you should use it like this:
```php
class Model1 extends YActiveRecord {
public function complexRecordCreation() {
$result=true; // Suppose everything is ok.
$this->beginT
/**
* Rollback a transaction on the database.
*
* @throws CException
*/
public function rollback() {
if(YII_DEBUG) {
Yii::trace("Rollback transaction");
}
if($this->_transaction === null) {
throw new CException('No ongoing transaction
(');
// Do some work. ($result&=...)
$result&=$model2->complexRecordCreation();
// ...
if($result)
$result&=$model3->save();
// Do some other work. ($result&=...)
if(!$result) {
$this->rollback();
} else {
$this->commit();
}
return $result;
}
}
class Model2 extends YActiveRecord {
public function complexRecordCreation() {
$result=true; // Suppose everything is ok.
} elseif($this->_transaction!==true){
$this->
beginT_transaction
();
// Do some work. ($result&=...)
$result&=$model4->complexRecordCreation();
// Do some other work. ($result&=...)
if(!$result) {
$this->rollback();
} else {
}
$this->
commit();
}
return $res_transaction=nul
tl;
}
}
```
In the above code, '$result' indicates the success of the operation. You can use this to check if you should continue performing the operation or not in the complex operation - in the end it must indicate the overall success of the operation.
As the callers rely on this result, the overall transaction is rolled back even if one of the subtransactions fails.[...]