Q:

Does Zend_Db_Adapter::beginTransaction() stack?

Consider the following:
/** (Cas_Template_Tree::DeleteNode)
 * Deletes the given node from the tree, and all of it's children.
 *
 * @static
 * @throws Exception
 * @param Cas_Template_Node $node
 * @return void
 */
public static function DeleteNode(Cas_Template_Node $node)
{
    $table = new Cas_Table_Templates();
    $adapter = $table->getAdapter();
    $leftStr = $adapter->quoteIdentifier('Left');
    $rightStr = $adapter->quoteIdentifier('Right');
    try
    {
        $adapter->beginTransaction();
        $row = $table->find($node->GetId())->current();
        $dependantRowSelector = array(
            "$leftStr >= ?" => $row->Left,
            "$rightStr <= ?" => $row->Right
        );
        //Get the rows removed so that we can nuke the ACLs later.
        $rowsToDelete = $table->fetchAll($dependantRowSelector)->toArray();
        //Delete the rows.
        $table->delete($dependantRowSelector);
        //Delete access control lists on those rows.
        foreach ($rowsToDelete as $rowToDelete)
        {
            Cas_Acl::CreateExisting($rowToDelete['Acl'])->Delete();
        }
        $left = (int)$row->Left;
        $right = (int)$row->Right;
        $difference = $right - $left + 1;
        $table->update(array('Left' => new Zend_Db_Expr("$leftStr - $difference")),
                       array("$leftStr > ?" => $right));
        $table->update(array('Right' => new Zend_Db_Expr("$rightStr - $difference")),
                       array("$rightStr > ?" => $right));
        $adapter->commit();
    }
    catch (Exception $ex)
    {
        $adapter->rollBack();
        throw $ex;
    }
}

/** (Cas_Acl::Delete)
 * Removes this ACL (and all of its dependent access control entries) from the database.
 * @return void
 */
public function Delete()
{
    $aclTable = new Cas_Table_AccessControlLists();
    $aceTable = new Cas_Table_AccessControlEntries();
    $adapter = Zend_Db_Table_Abstract::getDefaultAdapter();
    $identifierName = $adapter->quoteIdentifier('Identifier');
    $aclName = $adapter->quoteIdentifier('Acl');
    try
    {
        $adapter->beginTransaction();
        $aceTable->delete(array("$aclName = ?" => $this->GetId()));
        $aclTable->delete(array("$identifierName = ?" => $this->GetId()));
    }
    catch (Exception $ex)
    {
        $adapter->rollBack();
        throw $ex;
    }
}

Notice how both of these require that transactions work, because otherwise the operation would not be atomic (which would be bad ;) ) However, there are two transaction blocks going on here. The original DeleteNode method calls Cas_Acl::Delete(), which also attempts to execute itself inside of a transaction block. Ideally, Zend_Db would be smart enough to recognize this case, and for this particular call ignore the begin transaction and commit/rollback calls inside Cas_Acl::Delete.
Would the above code be safe? Can it be significantly improved in any way?

A:

I use a custom transactionManager object and add this logic (start transaction if not yet started) there.
I found this type of object (Delegation?) quite useful. I use it as well to use specific DB connection with right access. All write and read query inside a transaction are using this TransactionManager object.
Call to roolback are done in the controller, but via the TransactionManager as well, which detects if it should really do it, and it is very useful as well because calling Rollback twice will make most of existing databases cry.
Finally we try to use a generic rule, make the transaction control code very "high-level" in the code (only on controllers) and not in more abstract sub-levels objects. So that generally we can call any ->Delete() action on most objecs and we know this object is not trying to handle the transaction and that WE should do it. But, effectively, this is only a general rule and sometimes enclosing transactions happen and the TransactionManagers help us to hide the problem (and can log them with stacktrace).

