ZF2 Skeleton Application with Ctrl\DomainService

After creating the domain models and updating the database accordingly, we need to create a ‘Ctrl\DomainModelService‘, for each entity, to manage it. This will allow for the models themselves to be unaware of their persistence.

ctrllib provides a DomainServiceLoader comparable to the ControllerLoader. It is configured through the ServiceManager, and thus gives us many options in return:

    • uses module configuration and can therefore override other module DomainServices
    • it ensures correct initialization of your DomainServices
    • DomainServices can be fetched from wherever you have a ServiceLocator
    • instances can be aliased or marked as shared
    • possibility to add custom initializers or even to override the whole factory

Going further with the Car and Company models created in the previous post, we are going to create a ‘DomainModelService’ for both of them.
To create a DomainService we need 2 things: a class, and a configuration key pointing to the class.

To create a DomainService class all you need to do is extend ‘Ctrl\Service\AbstractDomainModelService‘ and override the $entity property. this property should be the full namespace of the model it represents.


<?php
// module/App/src/App/Service/CompanyService.php
namespace App\Service;

use App\Domain;
use Ctrl\Service\AbstractDomainModelService;

class CompanyService extends AbstractDomainModelService
{
    protected $entity = 'App\Domain\Company';
}

// module/App/src/App/Service/CarService.php
namespace App\Service;

use App\Domain;
use Ctrl\Service\AbstractDomainModelService;

class CarService extends AbstractDomainModelService
{
    protected $entity = 'App\Domain\Car';
}

You are free to call you service whatever you want, as long as you set the correct configuration. the ‘DomainServiceLoader’ will look for a ‘domain_services’ configuration key. This key should contain service manager configuration values, analogue to the way you configure controllers.

// module/App/config/module.config.php
<?php
namespace App;

return array(
    //controllers as example
    'controllers' => array(
        'invokables' => array(
            'App\Controller\Index' => 'App\Controller\IndexController'
        ),
    ),
    'domain_services' => array(
        'invokables' => array(
            'CompanyService' => 'App\Service\CompanyService',
            'CarService' => 'App\Service\CarService',
        ),
        'aliases' => array(
            'CompanyService' => 'Company',
        ),
    ),
    //...
);

fetching DomainServices

Now that the ServiceManager knows how to manage our DomainServices, they are available in all classes implementing ‘ServiceLocatorAwareInterface’ (AbstractController, …), or where you have access to the ServiceManager (onBootstrap…).

$loader = $serviceManager->get('DomainServiceLoader');
$companyService = $loader->get('CompanyService');
$companyService === $loader->get('Company'); //using alias (and services are shared by default)

as a shortcut, there are a couple of classes that implement a helper method to fetch DomainServices (‘Ctrl\Service\AbstractDomainService’, ‘Ctrl\Controller\AbstractController’)

/**
 * Retrieves a registered DomainService by name
 *
 * @param $serviceName
 * @return AbstractDomainService|AbstractDomainModelService
 */
public function getDomainService($serviceName)

using DomainServices

A DomainModelService provides a very basic API to interact with the persistence layer.

namespace Ctrl\Service;

use Ctrl\Domain\PersistableModel;

abstract class AbstractDomainModelService
    extends AbstractDomainService
    implements \Zend\EventManager\EventManagerAwareInterface
{
    public function getAll()

    public function getById($id)

    public function persist(PersistableModel $model)

    public function remove(PersistableModel $model)
}

Time to build some cars.

// module/App/src/App/Controller/IndexController.php
<?php
namespace App\Controller;

use Zend\View\Model\ViewModel;

class IndexController extends AbstractController
{
    public function indexAction()
    {
        $service = $this->getDomainService('Company');

        return new ViewModel(array(
            'companies' => $service->getAll()
        ));
    }

    public function detailAction()
    {
        $service = $this->getDomainService('Company');

        $company = $service->getById($this->params()->fromRoute('id'));
        $cars = $company->getCars(); //Doctrine loads these automatically!

        return new ViewModel(array(
            'company' => $company
        ));
    }

    public function createAction()
    {
        $company = new \App\Domain\Company();
        $company->setName('My Company');

        $service = $this->getDomainService('Company');
        $service->persist($company);

        return $this->redirect();
    }
}

You can now start adding custom methods to your DomainServices and DomainModels as you wish. You can find some examples in the ctrlAuth Module.
The persistence of the relations is managed by Doctrine, so you can refer to their documentation for any restrictions or more info on persisting relations and such.
The only additional prerequisite for using these DomainModelService classes is that an entity has a property ‘id’ as identifier with a public getter.