2013-03-21 79 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(); 
    } 
} 

最后,你的直觉是正确的(重:这导致重复的代码在很多地方,我需要交易的行动位)。架构您的模型以支持您拥有的大量事务性需求,并让控制器仅确定它将在自己的上下文中使用哪个入口点(方法)。