문제

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?

도움이 되었습니까?

해결책

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

다른 팁

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)

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top