Difference between #4 and #3 of How to use nested DB transactions (MySQL 5+, PostgreSQL)

changed
Title
How to use nested DB transactions (MySql(MySQL 5+,
PgSql)PostgreSQL)
changed
Category
TutorialsHow-tos
unchanged
Tags
db, mysql, postgresql, transaction, nested
changed
Content
sourceSource code taken from: [PHP, PDO & Nested
Transactions](http://www.kennynet.co.uk/2008/12/02/php-pdo-nested-transactions/)Transactions](http://www.kennynet.co.uk/2008/12/02/php-pdo-nested-transactions/).

testedTested with: MySqlMySQL
5.1.30 + Yii 1.1.8.

Problem:
Say there is service layer in an application, and one service may use others. No
each service deals will complex business logic will needs to wrap that into
transactions.

If there are services A and B here's how it might happen:
~~~
[php]
class ServiceA {
    public function transA() {
        $trans = Yii::app()->db->beginTransaction();
        // code
        Yii::app()->serviceB->transB();
        // code
        // exception handling and stuff
        $trans->commit();
    }
}

class ServiceB {
    public function transB() {
        $trans = Yii::app()->db->beginTransaction();
        // code
        // exception handling and stuff
        $trans->commit();
    }
}
~~~

With default Yii implementation, PDO will throw exception saying there already
is active transaction.

But there's a solution:)solution :)

First, you'll need to extend PDO class and save it in your the
protected/components directory:
~~~
[php]
class NestedPDO extends PDO {
    // Database drivers that support SAVEPOINTs.
    protected static $savepointTransactions = array("pgsql",
"mysql");

    // The current transaction level.
    protected $transLevel = 0;

    protected function nestable() {
        return in_array($this->getAttribute(PDO::ATTR_DRIVER_NAME),
                        self::$savepointTransactions);
    }

    public function beginTransaction() {
        if(!$this->nestable() || $this->transLevel == 0) {
            parent::beginTransaction();
        } else {
            $this->exec("SAVEPOINT LEVEL{$this->transLevel}");
        }

        $this->transLevel++;
    }

    public function commit() {
        $this->transLevel--;

        if(!$this->nestable() || $this->transLevel == 0) {
            parent::commit();
        } else {
            $this->exec("RELEASE SAVEPOINT
LEVEL{$this->transLevel}");
        }
    }

    public function rollBack() {
        $this->transLevel--;

        if(!$this->nestable() || $this->transLevel == 0) {
            parent::rollBack();
        } else {
            $this->exec("ROLLBACK TO SAVEPOINT
LEVEL{$this->transLevel}");
        }
    }
}
~~~

Then, you'll need to alter the behaviour of
[CDbConnection::createPdoInstance()]. YouNow you can do
this by making a subclass ofuse it in
protected/components/NestedDbConnection.php

There, change $pdoClass to your class name (NestedPDO in this example):
~~~
[php]
class NestedDbConnection extends CDbConnection
{
    protected function createPdoInstance()
    {
        $pdoClass='NestedPdo';
        if(($pos=strpos($this->connectionString,':'))!==false)
        {
            $driver=strtolower(substr($this->connectionString,0,$pos));
            if($driver==='mssql' || $driver==='dblib')
              $pdoClass='CMssqlPdoAdapter';
        }
        return new $pdoClass($this->connectionString,$this->username,
                             $this->password,$this->attributes);
    }
}
~~~

Note that $this->_attributes was also changed to $this->attributes so the
subclassing will work.

Now you can add the class name to the db configuration array in
protected/config/main.php`protected/config/main.php`:

~~~
[php]
'db'=>array(
            'class'=>'NestedDbConnection',
            'connectionString''pdoClass' => 'NestedPDO',
     'connectionString' => ...
        ),
),
~~~

That's it, there you go;)go ;)