Domain Layer with Doctrine2

UPDATE: ctrllib has been implemented in CtrlSkeleton, providing easy database management for Doctrine entities and implemented ViewHelpers for Twitter Bootstrap.

In a previous post I explained briefly why you need a Service Layer, since then a lot of things have changed, so here’s an update of how I handle things at the moment.

For starters I’m going to stop calling it a service layer and use the term Domain Layer, which is more adequate:

  • my implementation provides a base DomainModel and provides additional domain model related classes (e.g.: Form)
  • concerning service classes are related to the domain model
  • ZF2 adds its own ServiceManager so in the ZF2 world, a Service is something much broader

I’ve been working on a new implementation which can be found on my github: ctrl-f5/ctrllib. please check the github repo for a complete and up-to-date implementation.

This article gives a brief overview of how the base Domain related classes look, integration in ZF2 will follow.

Base Domain Models

My current implementation provides an interface which defines a valid DomainModel. This is currently an empty interface so the concrete models are not held up to any constraints other then just mark the interface as implemented.

<?php

namespace Ctrl\Domain;

interface Model
{

}

For the DomainModels I want to persist using doctrine I made an abstract class implementing this interface, and providing common properties.

<?php

namespace Ctrl\Domain;

abstract class PersistableModel implements Model
{
    /**
     * @var int
     */
    protected $id;

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }
}

The only thing I care about is an id, which represents the PK in the database. This is a single field since I always avoid using compound keys. I find compound keys just making the code more complex, with minimal befinits. (use unique contraints in the db instead)

So the we have it, all your DomainModels must do is implement the Ctrl\Domain\Model interface or, if you want to persist the model, extend the Ctrl\Domain\Persistable

Base Domain Services

As with the Models I currently have 2 levels implemented. The first is an abstract DomainService class with defines common functionality. For the prupose of this article I’m only going to implement an EntityManagerAwareInterface that allows me to interact with Doctrine

<?php

namespace Ctrl\Service;

use Ctrl\ServiceManager\EntityManagerAwareInterface;

abstract class AbstractDomainService implements
    \Ctrl\ServiceManager\EntityManagerAwareInterface
{
    /**
     * @var ServiceManager\ServiceLocatorInterFace
     */
    protected $serviceLocator = null;

    /**
     * @var \Doctrine\ORM\EntityManager
     */
    protected $entityManager = null;

    /**
     * @param \Doctrine\ORM\EntityManager $manager
     * @return AbstractService
     */
    public function setEntityManager(\Doctrine\ORM\EntityManager $manager)
    {
        $this->entityManager = $manager;
        return $this;
    }

    /**
     * @return \Doctrine\ORM\EntityManager|null
     */
    public function getEntityManager()
    {
        return $this->entityManager;
    }
}

The next DomainService provides default functionality for the DomainModels I created earlier.

<?php

namespace Ctrl\Service;

use Ctrl\Domain\Model;
use Ctr\Form\Form;
use DevCtrl\Domain\Exception;
use Zend\ServiceManager;

abstract class AbstractDomainModelService extends AbstractDomainService
{
    protected $entity = '';

    /**
     * @return array
     */
    public function getAll()
    {
        return $this->getEntityManager()
            ->createQuery('SELECT e FROM '.$this->entity.' e')
            ->getResult();
    }

    public function getById($id)
    {
        $entities = $this->getEntityManager()
            ->createQuery('SELECT e FROM '.$this->entity.' e WHERE e.id = :id')
            ->setParameter('id', $id)
            ->getResult();
        return $entities[0];
    }

    /**
     * @param Model $model
     * @return Form
     * @throws Exception
     */
    public function getForm(Model $model)
    {
        throw new Exception('Not implemented');
    }
}
Example Usage

So this is fairly simple, but it might be all you need. Here’s an example concrete implementation of a User Domain Model.

The DomainModel:

<?php

namespace MyProject\Domain;

use Ctrl\Domain\PersistableModel;

class User extends PersistableModel
{
    protected $name;
    public function getName() { return $this->name; }

    public function setName($name) {
        $this->name = $name;
        return $this;
    }
}

The DomainService:

<?php

namespace MyProject\Service;

use Ctrl\Service\AbstractDomainModelService;

class UserService extends AbstractDomainModelService
{
    protected $entity = 'MyProject\Domain\User';
}

This frees up you controllers and makes creating index and detail pages very easy:

<?php

namespace MyProject\Controller;

//AbstractController provides functionality to fetch DomainServices!
use Ctrl\Controller\AbstractController;

class UserController extends AbstractController
{
    public function indexAction()
    {
        return new ViewModel(array(
            'users' => $this->getDomainService('user')->getAll(),
        ));
    }

    public function detailAction()
    {
        return new ViewModel(array(
            'users' => $this->getDomainService('user')->getById(
                $this->params('id')
            ),
        ));
    }
}