关注点分离 - MVC结构中的flush()位置(控制器与服务层)

时间:2010-11-25 10:37:39

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

我有一个应用程序,我将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.
}

正如您所看到的,Controller对持久性一无所知,但为了获得此结果,我需要在每次调用服务层的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以有序的方式执行此操作,则为dunno)。这些部分的权限ID是错误的,而这些部分尚未保留。

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

2 个答案:

答案 0 :(得分:0)

我很乐意承认我对Zend,PHP或Doctrine2一无所知......

但是,这听起来好像需要实施工作单元模式。我使用ASP.NET和C#与MVC合作,并且有一些能做到这一点。

说过我的控制器只调用服务层,而是服务层来控制事务何时提交到持久性存储(在我的情况下是数据库)

答案 1 :(得分:0)

Antony建议像挂钩到EntityManager的__destruct()。但是,由于您不知道实体是否已更改,您不希望每次都调用flush,即使您只有只读方案。

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

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

你应该为此写一些便利功能。

然后你编写自己的事件Listener:

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();
         }
    }
}

现在在你的bootstrap中注册该监听器:

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

在控制器内部,您可以在每次请求的postDispatch回调中触发延迟刷新。

 $listener->flush();