2013-03-21 81 views
19

我正在使用Yii框架開發PHP/MySQL應用程序。在控制器不良行爲中做事務管理?

我遇到以下情況:

在我VideoController,我有一個actionCreate這將創建一個新的視頻和actionPrivacy它設置在視頻的隱私。問題是在actionCreate期間調用Video模型的setPrivacy方法,其中當前有一個事務。我希望將視頻的創建也納入交易中,這會導致錯誤,因爲交易已處於活動狀態。

在上this answer的意見,條例草案Karwin寫道

所以沒有必要使域模型類或DAO類管理 交易 - 做它在控制器級別

和在this answer

由於您使用的是PHP,因此您的交易範圍最多爲 單一請求。所以你應該只使用容器管理的交易,而不是服務層交易。也就是說,在處理請求的開始 處啓動事務,並在處理請求時完成 並提交(或回滾)。

如果我管理控制器中的交易,我將有一大堆的代碼看起來像:

public function actionCreate() { 
    $trans = Yii::app()->getDb()->beginTransaction(); 
    ...action code... 
    $trans->commit(); 
} 

這導致重複的代碼在很多地方,我需要交易的行動的地方。

或者我可以重構它到父Controller類,它會再自動執行每個動作創建交易beforeAction()afterAction()方法。

這種方法會有什麼問題嗎?什麼是PHP應用程序的事務管理的良好做法?

+0

奇妙的問題。我只是投票結束,因爲通常這種類型的討論在http://codereview.stackexchange.com/上得到了更好的處理,因爲它傾向於促進大量關於什麼是「最好」的開放式討論,而不是提供具體,客觀的基於代碼的答案。 – 2013-03-28 18:37:24

+1

我不知道那個網站。我會記得下次使用它。 – 2013-04-01 06:53:41

回答

17

,我說交易不會在模型層所屬的理由基本上是這樣的:

模型可以調用其他模型的方法。

如果模型試圖啓動一個事務,但它沒有它的調用者是否已經開始交易的知識,則該模型以有條件開始交易,如圖中的代碼示例@Bubba's answer。模型的方法必須接受一個標誌,以便調用者可以告訴它是否允許開始自己的事務。否則模型必須能夠查詢其調用者的「處於事務」狀態。

public function setPrivacy($privacy, $caller){ 
    if (! $caller->isInTransaction()) $this->beginTransaction(); 

    $this->privacy = $privacy; 
    // ...action code.. 

    if (! $caller->isInTransaction()) $this->commit(); 
} 

如果調用者不是對象會怎麼樣?在PHP中,它可能是一個靜態方法或簡單的非面向對象的代碼。這會變得非常混亂,並導致模型中出現很多重複的代碼。

這也是Control Coupling的一個例子,這被認爲是不好的,因爲調用者必須知道被調用對象的內部工作情況。例如,某些模型的方法可能具有$ transactional參數,但其他方法可能沒有該參數。調用者應該如何知道參數重要?

// I need to override method's attempt to commit 
$video->setPrivacy($privacy, false); 

// But I have no idea if this method might attempt to commit 
$video->setFormat($format); 

我所看到的建議(甚至像行走一些框架實現的)另一個解決辦法是讓beginTransaction()commit()無操作時DBAL知道它已經在一個事務中。但是如果你的模型試圖提交併發現它沒有真正提交,這可能會導致異常。或者嘗試回滾並忽略該請求。我以前寫過關於這些異常情況的文章。

我暗示的妥協是模型不知道交易。該模型不知道其對setPrivacy()的請求是否應該立即提交,或者是否是更大圖片的一部分,涉及多個模型的更復雜的一系列更改以及如果所有這些更改都成功,則應該提交只有這就是交易的要點。

因此,如果模型不知道他們是否可以或應該開始並提交他們自己的事務,那麼誰呢? GRASP包含一個Controller pattern,它是一個用例的非UI類,它被分配負責創建和控制所有塊以完成該用例。 控制器知道關於事務,因爲這是關於完整用例是否複雜並且需要在模型中,在一個事務內(或者在多個事務內)內完成多個更改的地方的所有信息的地方。

我已經寫之前的示例,即在MVC控制器的beforeAction()方法來啓動一個事務,並在afterAction()方法提交,是一個簡化。控制器應該可以自由地啓動和提交儘可能多的事務,因爲它在邏輯上需要完成當前操作。或者有時控制器可以避免顯式事務控制,並允許模型自動提交每個更改。

但問題是,有關什麼tranasction(s)是必要的信息是模型不知道的東西 - 他們必須被告知(以$事務參數的形式),否則查詢它從他們的調用者,無論如何都必須將問題委託給Controller的行動。

您也可以創建一個Service Layer類,每個類都知道如何執行這樣複雜的用例,以及是否將所有更改包含在單個事務中。這樣你避免了很多重複的代碼。但是PHP應用程序不包含獨特的服務層並不常見;控制器的行爲通常與服務層重合。

+1

好的,如果控制器是開始並提交事務的控制器,那麼這是否意味着控制器依賴於存儲/連接適配器? – CMCDragonkai 2014-01-03 14:04:36

+1

@CMCDragonkai,控制器至少依賴於存儲*接口*。實現可能會有所不同,但是,控制器(或服務層)會調用該接口來開始並提交邏輯事務。你可以做的OO去耦的數量有一個實際的限制。最終你必須開始做生意。 :-) – 2014-01-03 14:54:58

3

不,你說得對。交易由控制器應該做的「創建」方法委託。你使用像beforeAction()這樣的'包裝器'的建議是最好的選擇。只需讓控制器擴展或實現這個類。看起來您正在尋找Observer類型模式或工廠類實現。

0

那麼,這些廣泛的事務(在整個請求中)的一個缺點是限制了數據庫引擎的併發能力,並且還會增加死鎖概率。從這個角度來看,它可能只會在您需要它們的地方進行交易,並讓它們只涵蓋需要覆蓋的代碼。

如果可能的話,我肯定會去交易模型。重疊事務的問題可以通過在該模型中引入BaseModel(所有模型的祖先)和變量transactionLock來解決。然後,只需將您的begin/commit事務指令包裝到尊重此變量的BaseModel方法中即可。

7

最佳實踐:把交易的模式,不要把交易的控制器。

MVC設計模式的主要優點是:MVC使模型類可以不加修改地重複使用。使維護和實施新功能變得簡單。

例如,想必您主要開發用於其中用戶在一個時間輸入數據中的一個集合的瀏覽器,並且在移動數據操作到控制器中。後來你意識到你需要支持允許用戶從命令行上傳大量要導入到服務器上的數據。

如果所有的數據操作是在模型中,你可以簡單地發出聲音的數據,並把它傳遞給模型來處理。如果控制器中存在需要(事務)功能,則必須在CLI腳本中複製該功能。

在另一方面,也許你最終與另一個控制器需要執行相同的功能,從不同的角度。您現在需要在其他控制器中複製代碼。

爲此,您只需要解決模型交易的挑戰。

假設你有一個已經建立交易在setPrivacy()方法的視頻類(模型);你想從另一種方法調用它留存(),它需要在一個更大的交易也包裹它的功能,你可以只修改setPrivacy()來執行條件的交易。

也許是這樣的。

class Video{ 
    private $privacy; 
    private $transaction; 

    public function __construct($privacy){ 

     $this->privacy = $privacy; 
    } 

    public function persist(){ 
     $this->beginTransaction(); 
     // ...action code... 
     $this->setPrivacy($this->privacy, false); 
     // ...action code... 
     $this->commit(); 
    } 

    public function setPrivacy($privacy, $transactional = true){ 
     if ($transactional) $this->beginTransaction(); 

     $this->privacy = $privacy; 
     // ...action code.. 

     if ($transactional) $this->commit(); 
    } 


    private function beginTransaction(){ 
     $this->transaction = Yii::app()->getDb()->beginTransaction(); 
    } 

    private function commit(){ 
     $this->transaction->commit(); 
    } 
} 

最後,你的直覺是正確的(重:這導致重複的代碼在很多地方,我需要交易的行動位)。架構您的模型以支持您擁有的大量事務性需求,並讓控制器僅確定它將在自己的上下文中使用哪個入口點(方法)。