1
votes

I'm trying to inject the service manager into a controller.

Actual Error:

\vendor\zendframework\zend-servicemanager\src\Exception\ServiceLocatorUsageException.php:34

Service "Project\Service\ProjectServiceInterface" has been requested to plugin manager of type "Zend\Mvc\Controller\ControllerManager", but couldn't be retrieved. A previous exception of type "Zend\ServiceManager\Exception\ServiceNotFoundException" has been raised in the process. By the way, a service with the name "Project\Service\ProjectServiceInterface" has been found in the parent service locator "Zend\ServiceManager\ServiceManager": did you forget to use $parentLocator = $serviceLocator->getServiceLocator() in your factory code?

The process goes:

class BaseController extends AbstractActionController implements ServiceLocatorAwareInterface
{

    public function __construct(\Zend\ServiceManager\ServiceLocatorInterface $sl)
    {
        $this->serviceLocator = $sl;
    }
}
  1. Create controller and use constructor method
  2. Extend this BaseController to AdminController
  3. Setup Routes to AdminController => /admin
  4. use Module.php

    public function getControllerConfig()

  5. Use closer as factory to create controller object injecting the serviceLocator

    'Project\Controller\Project' => function($sm) { $serviceLocator = $sm->getServiceLocator(); return new \Project\Controller\ProjectController($serviceLocator); },

  6. try to use $this->getServiceLocator()->get('service_name')
  7. Exception found for missing service.....

Now the problem is this:

/**
 * 
 * @param ServiceLocatorInterface $sl
 */
public function __construct(\Zend\ServiceManager\ServiceLocatorInterface $sl)
{
    $rtn = $sl->has('Project\Service\ProjectServiceInterface');
    echo '<br />in Constructor: '.__FILE__;var_dump($rtn);
    $this->serviceLocator = $sl;
}
public function getServiceLocator()
{
    $rtn = $this->serviceLocator->has('Project\Service\ProjectServiceInterface');
    echo '<br />in getServiceLocator: '.__FILE__;var_dump($rtn);
    return $this->serviceLocator;
}

Within the __constructor() the service IS FOUND. Within the getServiceLocator() method the service with the same name IS NOT FOUND....

in Constructor: Project\Controller\BaseController.php bool(true) in getServiceLocator: Project\Controller\BaseController.php bool(false)

Am I missing something? Is the SharedServiceManager doing something here?

The entire purpose of this exercise was due to this message:

Deprecated: ServiceLocatorAwareInterface is deprecated and will be removed in version 3.0, along with the ServiceLocatorAwareInitializer. ...

3
Could you please add the controller factory as well as the respective areas from your controller and service_manager configuration?Fge
Thanks for the quick reply. The service_manager config is stated in point 5 above and the factory is generated within the Module.php => getControllerConfig() method. The controller calles $this->getServiceLocator()->get('service_name') and the exception is thrown.JI-Web

3 Answers

1
votes

In Zend Framework 2 there are multiple service locators (docs here), one general (mainly used for your own services), one for controllers, one for view helpers, one for validators, ... The specific ones are also called plugin managers.

The error message you are receiving is just telling you that you are using the wrong service locator, the ones that retrieves controllers and not the general one. It is also suggesting you how to solve your problem:

did you forget to use $parentLocator = $serviceLocator->getServiceLocator() in your factory code

What is probably happening (not 100% sure about this) is that in the constructor you are passing in an instance of the general service manager, and everything works fine with it. Then, since the controller implements the ServiceLocatorAwareInterface, the controller service locator is injected into your controller, overriding the one that you defided before.

Moreover, I think that the idea beyound the decision of removing ServiceLocatorAwareInterface in version 3 is that you don't inject the service locator inside your controller, but instead you inject directly the controller dependencies.

2
votes

If you really need the ServiceLocator, you have to inject it with a factory
Something like this

Controller:

<?php
namespace Application\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\ServiceManager\ServiceLocatorInterface;

class BaseController extends AbstractActionController
{
    protected $serviceLocator = null;

    public function __construct(ServiceLocatorInterface $serviceLocator)
    {
        $this->setServiceLocator($serviceLocator);
    }

    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
    {
        $this->serviceLocator = $serviceLocator;
        return $this;
    }
    public function getServiceLocator()
    {
        return $this->serviceLocator;
    }
}

Factory:

<?php
namespace Application\Controller\Factory;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Application\Controller\BaseController;

class BaseControllerFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator);
    {
        $controller = new BaseController($serviceLocator->getServicelocator());
        return $controller;
    }
}

?>

in module.config.php

<?php
// ...

    'controllers' => [
        'factories' => [
            'Application\Controller\BaseController' => 'Application\Controller\Factory\BaseControllerFactory',
            // ...
        ],
// ...
1
votes

You should try to prevent injecting the service manager or service locator in the controller. It would be much better to inject the actual dependencies (in your case 'Project\Service\ProjectServiceInterface') directly into the __construct method of your class. Constructor injection (the dependencies are provided through a class constructor) is considered best practice in ZF2.

This pattern prevents the controller from ever being instantiated without your dependencies (it will throw an error).

If you inject a ServiceLocator or ServiceManager from which you will resolve the actual dependencies in the class, then it is not clear what the class actually needs. You can end up in a class instance with missing dependencies that should never have been created in the first place. You need to do custom checking inside the class to see if the actual dependency is available and throw an error if it is missing. You can prevent writing all this custom code by using the constructor dependency pattern.

Another issue is that it is harder to unit-test your class since you cannot set mocks for your individual dependencies so easily.

Read more on how to inject your dependencies in my answer to a similar question.

UPDATE

About the issue you encountered. Controller classes implement a ServiceLocatorAwareInterface and during construction of your controller classes the ControllerManager injects a ServiceLocator inside the class. This happens here in the injectServiceLocator method at line 208 in ControllerManager.php. Like @marcosh already mentioned in his answer, this might be a different service locator then you injected. In this injectServiceLocator method you also find the deprecation notice you mentioned in your question.

Yours is available in the __construct method because at that time (just after constructing the class) the variable is not yet overwritten. Later when you try to access it in your getServiceLocator method it is overwritten.