2011-02-08 72 views
2

考慮以下幾點:是否Zend_Db_Adapter :: beginTransaction()堆棧?

/** (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; 
    } 
} 

注意這兩個是如何要求的交易工作,因爲否則操作不會是原子(這將是壞的;))然而,有兩個交易模塊怎麼回事。原始的DeleteNode方法調用Cas_Acl::Delete(),它也嘗試在事務塊內執行自己。理想情況下,Zend_Db足夠聰明以識別這種情況,對於這個特定的調用忽略Cas_Acl::Delete內的開始事務和提交/回滾調用。

上面的代碼是否安全?它能以任何方式顯着改善嗎?

+0

你有時直接調用Delete()嗎?還是隻在`DeleteNode()`中調用? – singles 2011-02-08 07:22:32

+0

@singles:是 - 只要需要刪除ACL,就會調用「Cas_Acl :: Delete」。還有一個ACL附加到這個應用程序管理的大多數對象。其中一些對象使用交易,而另一些則不需要。 – 2011-02-08 08:38:14

+0

你的代碼提出了我的問題,就像tintin http://omg.wthax.org/haddock.jpg中的haddock的鬍子一樣,我認爲「$ adapter-> beginTransaction();」應該在「try {} catch」之前設置,而不是在 – regilero 2011-02-08 12:49:46

回答

1

我使用自定義transactionManager的對象和添加此邏輯(如果尚未開始,則啓動事務)。

我發現這種類型的對象(Delegation?)非常有用。我也使用它來使用具有正確訪問權限的特定數據庫連接。一個事務中的所有寫入和讀取查詢都使用這個TransactionManager對象。

調用roolback是在控制器中完成的,但也通過TransactionManager來檢測它是否真的應該這樣做,並且它也非常有用,因爲調用Rollback兩次將使大多數現有數據庫都哭泣。

最後,我們嘗試使用一個通用的規則,使交易控制代碼非常「高級別」的代碼(僅限控制器),而不是在更抽象的分層次的對象。所以我們通常可以在大多數對象上調用任何 - > Delete()動作,並且我們知道這個對象不是試圖處理事務,而是我們應該這樣做。但是,實際上,這只是一個通用規則,有時會發生封閉事務,TransactionManagers幫助我們隱藏問題(並且可以使用堆棧跟蹤來記錄它們)。

2

AFAIK Zend_Db無法識別嵌套事務。看代碼。

public function beginTransaction() 
{ 
    $this->_connect(); 
    $q = $this->_profiler->queryStart('begin', Zend_Db_Profiler::TRANSACTION); 
    $this->_beginTransaction(); 
    $this->_profiler->queryEnd($q); 
    return $this; 
} 

有沒有代碼在這裏承認另一交易(但也許分析器可以用來做這種事)和_beginTransaction依靠PDO的beginTransaction

東西,你可能做的就是添加第二個參數去Delete()方法,確定是否使用交易與否和DeleteNode()false PARAM稱之爲:

//RB called as Cas_Acl::CreateExisting($rowToDelete['Acl'])->Delete(false); 
public function Delete($useTransaction = true) 
{ 
    $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 
    { 
     if ($useTransaction === true) { 
      $adapter->beginTransaction(); 
     } 
     $aceTable->delete(array("$aclName = ?" => $this->GetId())); 
     $aclTable->delete(array("$identifierName = ?" => $this->GetId())); 
     //BTW isn't commit() should be called here? 
    } 
    catch (Exception $ex) 
    { 
     if ($useTransaction === true) { 
      $adapter->rollBack(); 
     } 
     throw $ex; 
    } 
}