Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ZF2 when to use getServiceLocator() and when not to

I am really confused on when to use getServiceLocator and when not to. As an example:

+ Module
-+ Helloworld
--+ src
---+ Controller
----+ IndexController.php
----+ IndexControllerFactory.php

---+ Service
----+ LogginService.php
----+ GreetingService.php
----+ GreetingServiceFactory.php

GreetingServiceFactory.php has the content:

<?php
namespace Helloworld\Service;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;


class GreetingServiceFactory implements FactoryInterface
{

    public function createService (ServiceLocatorInterface $serviceLocator)
    {
        $greetingService = new GreetingService();

        $greetingService->setEventManager($serviceLocator->get('eventManager'));

        $loggingService = $serviceLocator->get('loggingService');

        $greetingService->getEventManager()->attach('getGreeting', array(
            $loggingService,
            'onGetGreeting'
        ));

        return $greetingService;
    }
}

And IndexControllerFactory.php has the content:

<?php
namespace Helloworld\Controller;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class IndexControllerFactory implements FactoryInterface
{

    public function createService (ServiceLocatorInterface $serviceLocator)
    {
        $ctr = new IndexController();

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

As you can see, I need $serviceLocator->getServiceLocator() in my ControllerFactory but not in my ServiceFactory. Why? Both use the same interface ServiceLocatorInterface which does not even define the getServiceLocator() method.

module.config.php:

'controllers' => array(
    'factories' => array(
        'Helloworld\Controller\Index' => 'Helloworld\Controller\IndexControllerFactory'
    )
)
,
'service_manager' => array(
    'invokables' => array(
        'loggingService' => 'Helloworld\Service\LoggingService'
    ),
    'factories' => array(
        'greetingService'=> 'Helloworld\Service\GreetingServiceFactory'
    ),
)

I'd appreciate any clarification :)

Have a nice day!

like image 808
machete Avatar asked Feb 16 '13 15:02

machete


3 Answers

The method getServiceLocator is defined on the AbstractPluginManager, since it implements the ServiceLocatorAwareInterface. As Maks3w pointed out, it is not part of the ServiceLocatorInterface, so avoid using it when implementing a service factory.

You can anyway define your factory as closure and still use it:

class MyModule
{
    public function getControllerConfig()
    {
        return array(
            'factories' => array(
                'IndexController' => function (
                    \Zend\ServiceManager\AbstractPluginManager $pm
                ) {
                    $ctr = new IndexController();

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

                    return $ctr;
                },
            ),
        );
    }
}

While in this example $pm is indeed a ServiceLocatorInterface instance, you will still need to get a reference to the "main" service manager to access the 'greetingService'.

ZF2 uses different service managers or plugin managers for controllers, services, view helpers, controller plugins, etc... That is mainly for type-hinting (look at the interface of the AbstractPluginManager to understand how type strictness is achieved) and for security.

In this case, the security issue is disallowing access to services that are not controllers, especially with routes with a dynamic controller parameter. That's why controllers are kept in a separate plugin manager.

Since the controller plugin manager is created from the "main" service manager, it also is initialized thanks to the ServiceLocatorAwareInterface.

To make this more clear, I've added a graph of the relations (does not include the factory and don't take it as valid UML):

Pseudo-UML

like image 68
Ocramius Avatar answered Oct 20 '22 03:10

Ocramius


As you can see, I need $serviceLocator->getServiceLocator() in my ControllerFactory but not in my ServiceFactory. Why?

The controller factory is called by a different service manager instance (the "ControllerLoader") to the main one. This is so that the dispatcher can't result in an arbitrary class being instantiated by the main service manager.

As a result, the controller factory's $serviceLocator is not the one you need when you want to retrieve 'greetingService', as 'greetingService' is registered with the main service manager. To get the main server manager from the controller one, you use getServiceLocator() and you now have an instance of the main service manager from which you can get() 'greeting service'

This is known as 'peering'. i.e. the "ControllerLoader" service manager (the one configured via the 'controllers' key in the config or getControllerConfiguration() in a Module class) is set up with the main service manager as a peer.

like image 30
Rob Allen Avatar answered Oct 20 '22 02:10

Rob Allen


Denitively you shouln't use getServiceLocator since that method is not defined in ServiceLocatorInterface instead use get()

like image 37
Maks3w Avatar answered Oct 20 '22 04:10

Maks3w