Shane A. Stillwell
Thoughts on Zend Framework & Doctrine 2 Design

Thoughts on Zend Framework & Doctrine 2 Design

Hold on Cowboy

This blog post is pretty old. Be careful with the information you find in here. The Times They Are A-Changin'

I usually structure my Zend Framework projects like the image on the right. This comes almost straight from the ZF documentation for recommended project structures framework.

I’ve been stressing using PHPunit more and more in my projects and endeavour to include tests for all my classes. When you are connected to a DB, it’s harder to test, but with the help of the Doctrine2 workflow, I think I’ve found a nice way to keep DB updates out of my Unit Tests.

How to test without touching a DB?

First, you need to have really skinny controllers. What does that mean? Well, controllers should really only do a few things.

  • Work with both the Request and the Response
  • Instantiate forms and populate them with data
  • Talk to the Views
  • Hand everything else off to a class in the services directory
Did you catch that last part? Most of the heavy lifting you're tempted to put in the controller should be shifted out to a services class (e.g. Application_Service_Email). That way you can keep your controllers nice and lean.
Now the services classes will be the ones doing instantiating classes to get the work done.

How does Doctrine fit into this?

Glad you asked. Doctrine uses a particular workflow when writing out values to the DB (MySql, Mongo, Postgres).

First you have to tell Doctrine to persist your object, in our case here the $user object. This is done using the DocumentManager ($dm). How do you get hold of $dm?  That’s another lesson soon to come.

_$dm->persist($user);_

You can do this to multiple objects and Doctrine keeps them in a queue. Then you have to write your changes to the DB. We do this with the flush command

_$dm->flush();_

That physically goes out to the DB and creates/updates/deletes all the objects in the queue. A very efficient way to work with the DB.

That’s great, but how does this fit into Unit Testing?

Simple. We persist in the Service class and flush in the controller. Here’s how this would look in the code? I thought you’d never ask.

<?php 

class Application_Service_User extends Application_Service_Abstract
{
    public function createUser(\Application_Model_User $user, $data)
    {
        $user = $user->createUser($data);
        $this->dm->persist($user);
        return $user;
    }

}
<?php

class AuthController extends Zend_Controller_Action
{

    public function createAction()
    {
        $form   = new \Application_Form_User();

        if (!$this->getRequest()->isPost()) {
            $this->view->form = $form;
            return;
        }

        if (!$form->isValid($_POST)) {
            $this->view->form = $form;
            return;
        }

        try {
            $userService        = new \Application_Service_User();
            $user = $userService->createUser(new \Application_Model_User(), $form->getValues());
            $this->dm->flush(array('safe' => true)); // Catch duplicates in MongoDB

        } catch (MongoException $e) {
            $form->reset();
            $this->view->form = $form;

        }
    }

}

The important parts to look at in the above code is the in the ‘User.php’ (Service Class), we are persisting our objects there. In the Controller we are flushing them.

This makes it easy to test since you’re not actually going to hit the DB unless you test the Controller (That’s integration testing, not Unit testing).

One Caveat

Sometimes you need the Id of an object. You’re only able to get this when you actually insert the object into the DB. So in your Service class it may be necessary to issue the flush command, but there are ways around this tool.