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

Php 关注点分离-在MVC结构中刷新()的位置(控制器与服务层),php,model-view-controller,controller,separation-of-concerns,doctrine-orm,Php,Model View Controller,Controller,Separation Of Concerns,Doctrine Orm,我有一个应用程序,其中我使用PHP和Zend框架以及Doctrine2作为ORM。我的问题是,控制器最好对底层模型和持久层了解多少。理想情况下,我自己会说这“没什么”——控制器不应该知道实体是如何/何时持久化的。然而,我觉得这并不总是最好的解决方案 我尝试遵循“关注点分离”的设计准则。我通过创建一个在我的模型上执行CRUD操作的服务层来实现这一点。请参见以下示例: public function testbuildAction() { // create section

我有一个应用程序,其中我使用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失败,因为它会以错误的顺序将对象持久化到数据库中-如果我可以指示Doctrine2以有序的方式执行此操作,则在节dunno之前会持久化特权??。权限为分区获取了错误的ID,但尚未持久化


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

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

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


我的控制器只调用服务层,事务何时提交到持久性存储数据库取决于服务层在我的情况下

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

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


说到这里,我的控制器只调用服务层,事务何时提交到持久性存储数据库取决于服务层。在我的例子中,Antony建议类似于挂接EntityManager的u销毁。然而,由于您不知道实体是否发生了更改,所以不希望每次都调用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);
在您的控制器中,您可以在每次请求时在后调度回调中触发延迟刷新(如果必要)

 $listener->flush();

Antony提出了一些类似于钩住EntityManager的破坏的建议。然而,由于您不知道实体是否发生了更改,所以不希望每次都调用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);
在您的控制器中,您可以在每次请求时在后调度回调中触发延迟刷新(如果必要)

 $listener->flush();

Doctrine2实现了工作单元模式,但是如何让这个模式生效呢?您说您的服务层控制这一点,但它知道控制器何时完成添加/删除/更新实体吗?我能看到的唯一方法是在控制器中执行显式entityManager->flush来实现这一点??我实际上使用Castle Windsor生活方式来控制工作单元实现的提交时间。它实现IDisposable接口,并在处理该接口时提交。对不起,我再也帮不了你了。就像我说的,我不熟悉Zend等,Doctrine2实现了工作单元模式,但是如何让这个模式生效呢?您说您的服务层控制这一点,但它知道控制器何时完成添加/删除/更新实体吗?我能看到的唯一方法是在控制器中执行显式entityManager->flush来实现这一点??我实际上使用Castle Windsor生活方式来控制工作单元实现的提交时间。它实现了IDis posable接口,并在处理该接口时提交。对不起,我再也帮不了你了。就像我说的我不熟悉Zend等。