Simplified transaction mechanism

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.

That definitely simplifies the transaction code. For my purposes I generally like to throw specific error messages for this sort of code, so I’d probably redesign it to rethrow exceptions after rolling back and just return normally on success. I may well use something like this in an upcoming project.

Really nice idea, thanks for sharing! I am currently working on a bidding system with a lot of transactions and this could definitely be a time-saver. Maybe you could add that one to the wiki? I bet I’ll never find that one again in the forums if searching for transactions ;)

That’s a really good point - we should wrap the call to the Closure in a try/catch and re-throw after rolling back the transaction.

I don’t generally catch exceptions - I usually throw them only for errors that can’t (or shouldn’t) be handled - and uncommitted transactions automatically roll back, so I haven’t had the need. But this would be a simple addition and definitely should be done…

[color="#008000"]NOTE: moved to proper section (Tips, Snippets and Tutorials instead of General Discussion for Yii 1.1.x)[/color]

I was actually proposing this feature be incorporated in Yii 1.1.x…

Should it then be moved to Feature request ?