I’ve had the following method on my Controller base-class for a while:
/**
* Wrap a funtion in a database-transaction; commit or rollback based on the result.
*
* @param Closure $function the function must return true in order to commit
* @return bool true if the transaction was successfully committed, otherwise false
*/
protected function transact(Closure $function)
{
$t = Yii::app()->db->beginTransaction();
if ($function() === true) {
$t->commit();
return true;
}
$t->rollback();
return false;
}
This is a huge time-saver, and it makes for much more legible and comprehensible transaction operations - where you would otherwise need intermediary variables and deep nested if/then/else-blocks, you can have something much simpler, for example:
protected function saveFoo(Foo $foo)
{
return $this->transact(function() use ($foo) {
$foo->attributes=$_POST['Foo'];
if (!$foo->save()) {
return false; // Foo not saved; abort transaction
}
// Update related Bars:
foreach ($_POST['Foo']['bars'] as $x => $y) {
$bar = new Bar();
$bar->x = $y;
$bar->foo_id = $foo->id;
if (!$bar->save()) {
return false; // Bar not created; abort transaction
}
}
return true; // success! commit transaction!
});
}
This is a very simple example, but I’m sure you’ve experienced how creating/updating/deleting deeply nested objects in one transaction can lead to intermediary variables like $success and $failure, and lots of dispersed rollback() calls at different levels… it gets messy.
I have my transact() method on the Controller base-class, which turns out to be somewhat impractical in some situations - if this idea is integrated into Yii, i would suggest having this method on CDbConnection instead.