Zend Framework 1.11 + Doctrine 2 – Let’s play nice, part 2

Hello! It’s been a while since my last post but I ran into a rather frustrating issue yesterday and didn’t want anyone else to have to repeat it. According to the official Doctrine 2 docs you should try to flush a limited number of times per request.

Note Do not invoke flush after every change to an entity or every single invocation of persist/remove/merge/… This is an anti-pattern and unnecessarily reduces the performance of your application. Instead, form units of work that operate on your objects and call flush when you are done. While serving a single HTTP request there should be usually no need for invoking flush more than 0-2 times.

Source

While debating how to go about this I remembered controller plugins and how handy they can be for controlling very stages of the request. For anyone who hasn’t read it I’d suggest checking out the post by Matthew O’Phinney. A quick summary of hooks can be found below:

  • routeStartup(): prior to routing the request
  • routeShutdown(): after routing the request
  • dispatchLoopStartup(): prior to entering the dispatch loop
  • preDispatch(): prior to dispatching an individual action
  • postDispatch(): after dispatching an individual action
  • dispatchLoopShutdown(): after completing the dispatch loop

The majority of the work from the EntityManager will come from the controller action’s so the hook we’re interested in is the postDispatch() hook. A few minutes later and we have our plugin.

// File located at My/Controller/Plugin/EntityManager.php

namespace My\Controller\Plugin;
use Zend_Controller_Plugin_Abstract, Zend_Controller_Front;

class EntityManager extends Zend_Controller_Plugin_Abstract
{

        /**
         * Flush the EntityManager.
         *
         * (non-PHPdoc)
         * @see Zend_Controller_Plugin_Abstract::dispatchLoopShutdown()
         */
        public function postDispatch($request)
        {
                $bootstrap = Zend_Controller_Front::getInstance()->getParam('bootstrap');
                $em = $bootstrap->getResource('EntityManager');
                $em->flush();
        }
}
// APPLICATION_PATH/configs/application.ini

resources.frontController.plugins[] = "My\Controller\Plugin\EntityManager"

Once that’s setup your ready to go with one small caveat.  If you’re a fan of $this->_helper->redirector() in the controller’s you going to have an issue because the redirector forces an exit after redirecting the response. In order for this to work properly you will need to change those to $this->_helper->redirector->setGotoSimple(); which doesn’t force an exit. You do not need to change all the redirector’s. Only ones where a flush is required. For example,

use Services\Game, Services\Profile;

class EditProfileController extends Zend_Controller_Action
{
        public $gameService;
        public $profileService;

        public function preDispatch()
        {
                $this->profileService = new Profile();

                $profile = $this->profileService->findProfile($this->_request->id);
                if (!$profile || !$profile->getGame() || !$this->profileService->isAllowed('update')) {
      // postDispatch isn't needed so we save a cycle or two and just call redirector per normal
                        return $this->_helper->redirector('index', 'profiles');
                }

                $form = $profile->getProfileForm(
                        array(
                                'game' => $profile->getGame(),
                                'method' => 'post',
                                'action' => '/edit-profile/process?id=' . $this->_request->id));

                $form->populateFromModel($profile);

                $this->view->profileForm = $form;
                $this->view->profileModel = $profile;
                $this->view->gameModel = $profile->getGame();
        }

        public function indexAction()
        {}

        public function processAction()
        {
                $request = $this->getRequest();
                if (!$request->isPost()) {
      // postDispatch isn't needed so we save a cycle or two and just call redirector per normal
                        return $this->_helper->redirector('index', 'profiles');
                }

                if (!$this->profileService->updateProfile($request->getPost())) {
                        return $this->render('index');
                }

     // The update profile call was successful so we need to make sure the postDispatch() gets ran.
                $this->_helper->redirector->setGotoSimple('index', 'profiles');
        }
}

Welp, that about sums it up! I hope I saved someone else the hassle I went through trying to understand why Doctrine 2 wasn’t flushing (quick! someone jiggle the handle). Cheers.

3 Responses to “Zend Framework 1.11 + Doctrine 2 – Let’s play nice, part 2”

  1. Cobby says:

    SpiffyJr, this is a great idea… don’t know why I didn’t think of this before.

    A small recommendation though, might I suggest injecting the EntityManager into the plugin (via constructor) instead of retrieving it via the front controller. I try an avoid using getInstance() where possible, IMO singletons are a bad idea.

    The only draw back is that you have to register the plugin manually during bootstrap, but I think it’s a worthy trade off for the code maintainability and testability.

  2. Cobby says:

    Also, instead of updating all your action code to use goToSimple, you can call $this->_helper->Redirector->setExit(false) so you can still use the $this->_redirect() function.

    If you need to exit, you can just pass the option to the _redirect function: $this->_redirect(‘/admin’, array(‘exit’ => true))

    Example: http://pastie.org/1454555

  3. James Howard says:

    Thanks for this post. You saved me a lot of time digging that out myself. Trying to do the same thing with a Doctrine auto-flush.