9

I'm developing a ZF2 system and it was working very well, but after I clone the repository in other computer this deprecated error has appeared:

You are retrieving the service locator from within the class Module\Controller\Controller. Please be aware that ServiceLocatorAwareInterface is deprecated and will be removed in version 3.0, along with the ServiceLocatorAwareInitializer. You will need to update your class to accept all dependencies at creation, either via constructor arguments or setters, and use a factory to perform the injections. in /home/path/project/vendor/zendframework/zend-mvc/src/Controller/AbstractController.php on line 258

The composer.json:

"require": {
    "php": ">=5.5",
    "ext-curl": "*",
    "ext-json": "*",
    "ext-mbstring": "*",
    "zendframework/zendframework": "~2.5",
    "doctrine/doctrine-orm-module": "0.*",
    "hounddog/doctrine-data-fixture-module": "0.0.*",
    "imagine/Imagine": "~0.5.0"

The error appears when I retrieve the service in my controllers (extending Zend\Mvc\Controller\AbstractActionController):

$this->getServiceLocator()->get("Module\Service\Service");

In the Zend core at Zend\Mvc\Controller\AbstractController is like this:

public function getServiceLocator()
{
    trigger_error(sprintf(
        'You are retrieving the service locator from within the class %s. Please be aware that '
        . 'ServiceLocatorAwareInterface is deprecated and will be removed in version 3.0, along '
        . 'with the ServiceLocatorAwareInitializer. You will need to update your class to accept '
        . 'all dependencies at creation, either via constructor arguments or setters, and use '
        . 'a factory to perform the injections.',
        get_class($this)
    ), E_USER_DEPRECATED);

    return $this->serviceLocator;
}

Before was only this:

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

I've tried everything, someone know what I've to do?

Euclécio
  • 174
  • 5
  • 15
  • It's right there in the message: `You will need to update your class to accept all dependencies at creation, either via constructor arguments or setters, and use a factory to perform the injections.` – Matteo Tassinari Mar 17 '16 at 13:04
  • I didn't understand very well this message, "need to update your class" but what class? – Euclécio Mar 17 '16 at 13:27
  • Possible duplicate of [PHP Deprecated: You are retrieving the service locator from within the class ZFTool\Controller\ModuleController](http://stackoverflow.com/questions/35933113/php-deprecated-you-are-retrieving-the-service-locator-from-within-the-class-zft) – Oleg Abrazhaev May 14 '16 at 03:36

2 Answers2

10

You don't have to do anything, yet. When you upgrade to ZF3, then you will have to change how your controller class receives its dependencies.

ZF2 supports two dependency injection patterns: by service locator and by constructor. ZF3 removes "by service location" and requires "by constructor". All this does, effectively, is change how dependencies resolve, moving the resolution from "just in time" to "at construction".

Instead of being able to get a service from anywhere, you instead receive them at construction. Update your code along the following lines:

namespace Module\Controller;

class Controller {
    public function __construct(\Module\Service\Service $service) {
        $this->service = $service;
    }
}

Use $this->service where you need it in the class's methods.

Then use a controller factory to create your controller, like so:

function ($controllers) { 
    $services = $controllers->getServiceLocator();
    return new \Module\Controller\Controller($services->get('Module\Service\Service')); 
} 

The change is discussed in Issue 5168, and this blog post discusses why service injection with a service locator is an anti-pattern.

bishop
  • 37,830
  • 11
  • 104
  • 139
  • Can you recommend a solution for a controller where different actions require different services? For example, my indexAction requires the user service, viewAction requires the user service and product service, and my aboutUsAction may require no services at all. My point is, for such a controller, it would not make sense to instantiate and pass all services in the constructor for every request. – Leo Galleguillos Apr 04 '16 at 21:07
  • 3
    @helloworld You're right, injecting all those services wouldn't make sense, and I think that's exactly the point of this change: to force developers to contemplate the Single Responsibility Principle as it applies to controllers. To me, it sounds like you've got a "fat" controller, and the solution is to create separate controllers for these actions. If you post a specific example to [codereview SE](codereview.stackexchange.com), you can get even more specific feedback. – bishop Apr 05 '16 at 00:15
  • Thank you for the feedback. I will work on making controllers abide by SRP. – Leo Galleguillos Apr 05 '16 at 16:46
1

You can create a controller plugin service() (but it's a bad practice, prefer FactoryInterface)

module.config.php

'controller_plugins' => [
    'factories' => [
        'service' => YourNamespace\Mvc\Controller\Plugin\Service\ServiceFactory::class,
    ],
],

YourNamespace\Mvc\Controller\Plugin\Service\ServiceFactory.php

<?php

namespace YourNamespace\Mvc\Controller\Plugin\Service;

use Interop\Container\ContainerInterface;
use YourNamespace\Mvc\Controller\Plugin\Service;
use Zend\ServiceManager\Factory\FactoryInterface;

class ServiceFactory implements FactoryInterface
{
    /**
     * @param  ContainerInterface $container
     * @param  string $requestedName
     * @param  array $options
     * @return Service
     */
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $plugin = new Service();
        $plugin->setServiceLocator($container);
        return $plugin;
    }
}

YourNamespace\Mvc\Controller\Plugin\Service.php

<?php

namespace YourNamespace\Mvc\Controller\Plugin;

use Interop\Container\ContainerInterface;
use Zend\Mvc\Controller\Plugin\AbstractPlugin;

/**
 * Plugin: $this->service();
 */
class Service extends AbstractPlugin
{
    /**
     * @var ContainerInterface
     */
    protected $serviceLocator;

    /**
     * @return ContainerInterface
     */
    public function getServiceLocator()
    {
        return $this->serviceLocator;
    }

    /**
     * @param  ContainerInterface $serviceLocator
     * @return Service
     */
    public function setServiceLocator(ContainerInterface $serviceLocator)
    {
        $this->serviceLocator = $serviceLocator;
        return $this;
    }

    /**
     * @param  string $name
     * @return object|bool
     */
    public function __invoke($name = null)
    {
        $sl = $this->getServiceLocator();
        if (!$name) {
            return $sl;
        }
        if (!$sl->has($name)) {
            return false;
        }
        return $sl->get($name);
    }
}
B.Asselin
  • 978
  • 10
  • 19