Who should care about database transactions?

This is something I always found annoying. When dividing your application in a proper object model, decease or even a procedural model for that matter, ask you end up with situations where functions and methods perform multiple queries on the database, buy or make other calls that modify the data. Transactions exists to make sure all those modifications are cancelled if one query happen to fail. There are two ways to deal with transactions: rely on the parent call to handle the transaction or handle it yourself. The first case breaks one of the basic purposes of design my asking the parent to understand the details of the operation and the second one will break as soon as the function is called from a context where a transaction is already active.

My solution to this is to get each function to believe they are in control of the transaction, which may or may not be true. The solution is very simple: let each call begin the transaction, but only the first one to call it in a tree really begins the transaction. The following class does exactly this. Each scope willing to isolate a transaction instanciates a Transaction object. If the function terminates in an expected way, a call to commit must be made. Otherwise, failure is assumed and the transaction will roll back, but only if the scope actually owns the transaction. Even commit is a nil operation if the scope does not own the transaction. Some of you might have noticed, this is strongly inspired by C++ auto_ptr.

class Transaction
{
private static $transactionActive;
private $transactionOwner;

function __construct()
{
if( !self::$transactionActive )
{
self::$transactionActive = true;
$this->transactionOwner = true;
get_db()->beginTransaction();
}
}

function __destruct()
{
if( self::$transactionActive && $this->transactionOwner )
{
self::$transactionActive = false;
get_db()->rollBack();
}
}

function commit()
{
if( self::$transactionActive && $this->transactionOwner )
{
self::$transactionActive = false;
get_db()->commit();
}
}
}

The get_db() function is a simple function to return the PDO object and lazy-load it as required.

I use this technique by throwing an exception whenever an error occurs. The controller-level is responsible for catching all exceptions and log or report them as required. Of course, some higher levels also catch exceptions and handle them or re-throw them with a more specific type and message (such as converting PDO duplicate entries to a readable message).

With this simple class, the entire system can ensure transaction isolation without having to worry about the context. I find it very helpful.

Of course, it only works in PHP 5 as destructors are required.

Leave a Reply

Your email address will not be published. Required fields are marked *