Why use the Service Manager in Zend Framework 2?

10.7k Views Asked by At

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

4

There are 4 best solutions below

6
On BEST ANSWER

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.

0
On

I think the second approach is better, which is making the controller class independent to the GreetingService. This approach will be useful when you want to use another greeting service to be consumed by the controller. You don't need to change the code in controller class at all, instead you do that by changing the service manager with that factory closure.

I believe this is the main idea of inversion of control or dependency injection, all wiring and configuration are done outside the class (in this case: controller class).

So, in my opinion, that's the reason why the second approach is the better one.

0
On

To add something to @yechabbis answer:

The factory pattern for the ServiceConfiguration is indeed for complex infrastructure or simply to not use closures / callable functions inside the configuration. This is for two reasons:

  • Readability / Cleaner Code
  • Performance

Setting up the factory pattern inside getServiceConfig is nice, clean and fast. No classes are instantiated but will so upon call of the service-key. If, however, you set up services using the closure pattern or callable function, then those classes will always be instantiated, upon every single request!

This probably is just an example thing, but the code you've shown would also best be served as a ViewHelper

0
On

Very useful question. He has been raised repeatedly. I think you can get an ACCURATE ANSWER HERE