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

发布于 2024-10-04 17:54:41 字数 2781 浏览 1 评论 0原文

我有一个应用程序,我使用 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() 和lush()服务层。我宁愿做这样的事情:

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。

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

I have an application where I use PHP with Zend framework and Doctrine2 as ORM. My question is related to how much the controller preferably should know about the underlying model and persistence layer. Ideally I would say this is 'nothing' myself - the controller should not know anything about how/when the entities are persisted. However I feel this is not always the best solution(?).

I've tried to follow the 'separation of concerns' design guideline. I've done this by creating a service layer that performs CRUD operations on my models. See the following example:

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.
}

As you can see the Controller does not know anything about persistence, but to obtain this result I need to perform both persist() and flush() inside every call to the createXXX() or updateXXX() methods of the service layer. I'd rather have done something like this:

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
}

But this causes Doctrine2 to fail as it does persist the objects to the database in the wrong order - privileges are persisted before sections (dunno if I can instruct Doctrine to perform this in an ordered manner??). The privileges gets wrong ID for the sections, who isn't persisted yet.

Anyway, the big issue here is whether I should attempt to postpone flushing until all objects have been created and relations have been set. The goal being to have ONE transaction that does all writing to the database - which consequently must be triggered by the controller (since it is the only one knowing WHEN object and relation building is done), thereby 'contaminating' the controller with knowledge of the persistence layer?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

心病无药医 2024-10-11 17:54:41

Antony 建议使用 EntityManager 的 __destruct() 进行挂钩。然而,由于您不知道实体是否很少更改,因此您不想每次都调用刷新,即使您只有只读场景。

因此,服务层不应该刷新,但控制器应该刷新,您可以轻松地使用 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();

Antony suggests something like hooking into __destruct() of the EntityManager. However since you don't know if entities raelly changed you don't want to call flush every time, even if you only have a read-only scenario.

Therefore the service layer shouldn't flush but the controller, you could easily use the Doctrine EventManager to have each service layer action dispatch an event "requireFlush":

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

You should probably write some kind of convenience function for this.

Then you write your own event 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();
         }
    }
}

Now register that listener in your bootstrap:

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

And inside your controller you can trigger the delay flush if necessary in a postDispatch callback at every single request).

 $listener->flush();
诗化ㄋ丶相逢 2024-10-11 17:54:41

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

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

话虽如此,我的控制器只是调用服务层,由服务层来控制事务何时提交到持久性存储(在我的例子中是数据库)

I'll readily admit I know absolutely nothing about Zend, PHP or Doctrine2...

BUT, this does sound like you need an implementation of the Unit of Work pattern. I work with MVC using ASP.NET and C# and have something that does that.

Having said that my controllers just call the service layer and it's up to the service layer to control when the transaction is committed to the persistence store (database in my case)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文