Service Layer and Zend Framework part2

info: for my new implementation with ZF2 and Doctrine2 please check here

Since my last post, I’ve had some time to reflect on my take on a ServiceLayer and do a little experimentation. This time it around it won’t be all talk and no action though, time to get your code-writing-mittens on. For the purpose of this post I have created a GitHub repository, as the series will progress (if it ever will of course) I should end up with a descent, flexible, robust implementation.
A few points are to be made clear before we get our hands dirty. I will only be using stable libraries, so no ZF2 for now, I’ll be sticking to ZF1.11. (Although Matthew Weier O’Phinney wrote a nice introduction about the new plugin broker architecture in ZF2 which will come in handy in situations like this…). Luckily the autoloader rewrite in ZF 1.10 will allow us to use PHP 5.3 namespaces in our library classes. Another new PHP feature that will come in handy will be Late Static Binding.

As you can see in the github repo, there’s a library folder Ctrl, which will contain only namespaced classes. ZF can load these automatically through the following line in the app.ini file:

autoloadernamespaces[] = "Ctrl"

The first file we’ll look at is the interface for a service: lib/Ctrl/Service/IService.php

<?php

namespace Ctrl\Service;

interface IService
{
    static function getType();
    static function factory(array $options = array());
    function getOptions();
}

For creating services it they will implement a factory method, which accepts an array of options. These options can later only be retrieved.
The getType() function will allow us to retrieve the fully qualified class name without having to work with string, which will make the code more maintainable.

On this interface the abstract service class is built: lib/Ctrl/Service/Service.php

<?php

namespace Ctrl\Service;

abstract class Service implements IService
{
    protected $_options = array();

    public function __construct(array $options = array())
    {
        $this->_setOptions($options);
        $this->init();
    }

    /**
     * Constructs a Service instance
     *
     * @param array $options
     * @return Ctrl\Service\Service
     */
    public static function factory(array $options = array())
    {
        $class = self::getType();
        return new $class($options);
    }

    protected function _setOptions(array $options)
    {
        $this->_options = $options;
    }

    public function getOptions()
    {
        return $this->_options;
    }

    public static function getType()
    {
        return get_called_class();
    }

    protected function init() {}
}

This is a pretty simple implementation but it will do for now. What this allows us to do is call the service from anywhere using the factory method:

$logService = Log_Service_Test::factory(array('logDir' => '/var/log'));
$logService->archiveLogbook(array('file' => 'errors.log'));

In the github repo, you can also find an action controller extension which adds a servicebroker to controllers. The service broker will allow to configure a service and retrieve stored services easily, while at the same time provide functionality to create custom services. That component is still somewhat of a work in progress, so maybe there will be an post about it later on… I’ll give you a little taste of what is going around at the moment of writing:

public function testServicesAction()
{
    //old way, stil lpossible of cource
    $service1 = Log_Service_Test::factory(array('test' => 'my first service'));

    //calling service for the first time will set the options
    $service2 = $this->_services->getService(Log_Service_Test::getType(), array('optionsTest' => 'viaBroker! > these options will be set'));

    //calling second time will return the previous instance
    $service3 = $this->_services->getService(Log_Service_Test::getType(), array('second' => 'viaBroker! > these options will be ignored'));

    //so $service2 === $service3 and has the options as defined in the call to create $service2

    //requesting a service through the getLocalService method will skip the internal register and will always create
    //the service with the options passed, this instance will not be saved by the broker
    $service4 = $this->_services->getLocalService(Log_Service_Test::getType(), array('third' => 'viaBroker! --local'));
}

So it’s a start, but not sure if this is a good way of doing it…