【问题标题】:Separation of concerns - where to flush() in a MVC structure (controller vs servicelayer)关注点分离 - 在 MVC 结构中刷新()的位置(控制器与服务层)
【发布时间】:2011-05-15 15:14:47
【问题描述】:

我有一个应用程序,我在其中使用 PHP 与 Zend 框架和 Doctrine2 作为 ORM。我的问题与控制器最好应该了解多少底层模型和持久层有关。理想情况下,我自己会说这“没什么”——控制器不应该知道实体是如何/何时被持久化的。但是我觉得这并不总是最好的解决方案(?)。

我已尝试遵循“关注点分离”设计准则。我通过创建一个在我的模型上执行 CRUD 操作的服务层来做到这一点。请参阅以下示例:

public function testbuildAction()
{        
    // create section
    $sectionService = new \MyAPP\Model\Service\Acl\SectionService();        
    $sectionA       = $sectionService->createSection('SectionA-NAME');

    // create privilege with the above section
    $privilegeService   = new \MyAPP\Model\Service\Acl\PrivilegeService();
    $privilegeA = $privilegeService->createPrivilege(
                            $sectionA, 
                            \MyAPPFrameWork\Model\Acl\Privilege::PERMISSION_EDIT
                        );

    // create a role with the privilege above. A role must have at least one priv.
    $roleService = new \MyAPP\Model\Service\Acl\RoleService();
    $role        = $roleService->createRole('Role-NAME', $privilegeA); 

    // this loads a managed User object (managed by EntityManager)
    $user = $this->_helper->IdentityLoader(); 
    $user->addRole($role); // add the role to this user
    $userService = new \MyAPP\Model\Service\Core\UserService();        
    $userService->updateUser($user); // persist the updates.
}

如您所见,控制器对持久性一无所知,但要获得此结果,我需要在每次调用服务层的 createXXX() 或 updateXXX() 方法时同时执行 persist() 和 flush() .我宁愿做这样的事情:

public function testbuildAction()
{        
    // create section
    $sectionService = new \MyAPP\Model\Service\Acl\SectionService();        
    $sectionA       = $sectionService->createSection('SectionA-NAME');

    // create privilege with the above section
    $privilegeService   = new \MyAPP\Model\Service\Acl\PrivilegeService();
    $privilegeA = $privilegeService->createPrivilege(
                            $sectionA, 
                            \MyAPPFrameWork\Model\Acl\Privilege::PERMISSION_EDIT
                        );

    // create a role with the privilege above. A role must have at least one priv.
    $roleService = new \MyAPP\Model\Service\Acl\RoleService();
    $role        = $roleService->createRole('Role-NAME', $privilegeA); 

    // this loads a managed User object (managed by EntityManager)
    $user = $this->_helper->IdentityLoader(); 
    $user->addRole($role); // add the role to this user

    // persist it all (all service-classes access the same entitymanager).
    $roleService->flush(); // everything is persisted
}

但这会导致 Doctrine2 失败,因为它确实以错误的顺序将对象持久保存到数据库 - 权限在部分之前保留(不知道我是否可以指示 Doctrine 以有序的方式执行此操作??)。权限获取的部分 ID 错误,尚未持久化。

无论如何,这里最大的问题是我是否应该尝试推迟刷新,直到所有对象都已创建并已设置关系。目标是让一个事务完成所有写入数据库 - 因此必须由控制器触发(因为它是唯一知道何时完成对象和关系构建的事务),从而“污染”控制器与持久层?

【问题讨论】:

    标签: php model-view-controller controller separation-of-concerns doctrine-orm


    【解决方案1】:

    我承认我对 Zend、PHP 或 Doctrine2 一无所知...

    但是,这听起来确实需要工作单元模式的实现。我使用 ASP.NET 和 C# 与 MVC 一起工作,并且有一些东西可以做到这一点。

    话虽如此,我的控制器只是调用服务层,并由服务层控制事务何时提交到持久性存储(在我的情况下为数据库)

    【讨论】:

    • Doctrine2 实现了工作单元模式,但是如何让这个模式生效呢?您说您的服务层对此进行控制,但它是否知道控制器何时完成添加/删除/更新实体?我能看到的唯一方法是在控制器中做一个明确的 entityManager->flush() 来实现这一点??
    • 我实际上使用 Castle Windsor 生活方式来控制工作单元实现何时提交。它实现了 IDisposable 接口并在它被释放时提交。对不起,我已经帮不上什么忙了。就像我说的我不熟悉 Zend 等。
    【解决方案2】:

    Antony 建议使用类似于挂钩到 EntityManager 的 __destruct() 的方法。但是,由于您不知道实体是否会发生变化,因此您不想每次都调用 flush,即使您只有只读方案。

    因此服务层不应该刷新但控制器,您可以轻松地使用 Doctrine EventManager 让每个服务层操作调度一个事件“requireFlush”:

    $em->getEventManager()->dispatchEvent("requireFlush", new OnFlushEventArgs($em));
    

    您可能应该为此编写一些方便的函数。

    然后你编写自己的事件监听器:

    class DelayFlushListener
    {
        private $requiresFlush = true;
        private $delayFlush = true;
    
        public function __construct($delayFlush = true) {
           $this->delayFlush = $delayFlush;
        }
    
        public function requireFlush(EventArgs $args) {
            $this->em = $args->getEntityManager();
            if ($this->delayFlush) {
                $this->requiresFlush = true;
            } else {
                $this->em->flush();
            }
        }
    
        public function flush() {
             if ($this->requiresFlush) {
                 $this->em->flush();
             }
        }
    }
    

    现在在您的引导程序中注册该侦听器:

     $listener = new DelayFlushListener();
     $em->getEventManager()->addEventListener(array("requireFlush"), $listener);
    

    在您的控制器内部,如果需要,您可以在每个请求的 postDispatch 回调中触发延迟刷新)。

     $listener->flush();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-08-09
      • 2019-04-13
      • 2015-11-25
      • 2017-04-12
      • 1970-01-01
      • 2012-01-10
      • 1970-01-01
      相关资源
      最近更新 更多