Why use the Service Manager in Zend Framework 2?

Patrioticcow picture Patrioticcow · Jan 27, 2013 · Viewed 10.6k times · Source

lets say i have a service:

namespace Helloworld\Service;

class GreetingService
{
    public function getGreeting()
    {
        if(date("H") <= 11)
            return "Good morning, world!";
        else if (date("H") > 11 && date("H") < 17)
            return "Hello, world!";
        else
            return "Good evening, world!";
    }
}

and i create an invokable for it

public function getServiceConfig()
{
    return array(
        'invokables' => array(
            'greetingService'
                => 'Helloworld\Service\GreetingService'
        )
    );
}

then in my controller i could do:

public function indexAction()
{
    $greetingSrv = $this->getServiceLocator()
        ->get('greetingService');

    return new ViewModel(
        array('greeting' => $greetingSrv->getGreeting())
    );
}

supposedly this make the controller dependent of the service (and the ServiceManager)

and the better solution is to create factory for that service or return a closure in the ServiceManager and create a setter in the controller:

class IndexController extends AbstractActionController
{
    private $greetingService;

    public function indexAction()
    {

        return new ViewModel(
            array(
                'greeting' => $this->greetingService->getGreeting()
            )
        );
    }

    public function setGreetingService($service)
    {
        $this->greetingService = $service;
    }
}

and

'controllers' => array(
    'factories' => array(
        'Helloworld\Controller\Index' => function($serviceLocator) {
            $ctr = new Helloworld\Controller\IndexController();

            $ctr->setGreetingService(
                $serviceLocator->getServiceLocator()
                    ->get('greetingService')
            );

            return $ctr;
        }
    )
)

My question is why? Why is the second approach better than the first one? and What does it mean that the controller is dependent of the service?

thanks

Answer

yechabbi picture yechabbi · Jan 27, 2013

The ServiceManager is by default injected within any ZF2 controller, as it extends the AbstractController implementing the ServiceLocatorAwareInterface interface.

The second approach has a form of "redundancy" because, besides having already access to a ServiceManager instance, whenever you need to share your service among your controllers, you need to configure for each one of them the injection mechanism. As your controllers have already a dependency vis-à-vis the ServiceManager, It would make more sense to use the first approach and register your Domain related Services to the ServiceManager, centralizing thus the access to the Service layer.

Note: The following part of the answer may go beyond the scope of the question, but it aims to provide the "hidden" background of the original one.

Let's assume we're building a complex system, within which low-coupling, re-usability & test-ability are promoted. Our system is multi-layered and we built everything until the service layer. Note that until now we did not consider yet the "MVC" web layer or even opt for a given framework.

Within our Service Layer (I'll consider this layer as it is the one mentioned in the question), we assume that we adopted the principle of separation between the Business Logic and the Object Graph construction (or dependency resolution). So we have probably a couple of complex services that are constructed through factories.

Now that our service layer is built, we decide to "plug" it above ZF2. Naturally, Our services should be accessible from the controllers, as your question illustrated it, we have more than a way for doing it. However, we want to avoid redundancy and leverage what we've already built. Let's assume the following factory :

//The following class is a part of our Service layer
public class ComplexServiceFactory{

    private dependencyA;
    private dependencyB;

    public function buildComplexService()
    {
        $service = new ComplexService();
        $service->setDependencyA($this->dependecyA);
        $service->setDependencyB($this->dependecyB);
        return $service;
    }

}

What we'll do now is just to adjust (actually extend) our factory so that it becomes usable by the ServiceManager logic. This class can be considered as part of the mechanism used to "plug" our system to ZF2 (It's actually an Adapter)

public class SMComplexServiceFactory extends ComplexServiceFactory implements
    Zend\ServiceManager\FactoryInterface
{

    public function createService(ServiceLocatorInterface $sm)
    {
        $this->setDependencyA($sm->get('dependecyA'));
        $this->setDependencyB($sm->get('dependecyB'));
        return parent::buildComplexService;
    }

}

By doing so, we're not bringing up the object graph construction to the MVC layer (otherwise it would be a Separation of Concerns violation and an unnecessary cross-layer coupling). The Service Manager + our "Adapted" factories classes are in some sense our dependency resolution mechanism. The fact is that our System is still portable, we could for example opt for another system (another framework) and have a low dependency vis-à-vis the MVC platform.