5

I am looking at this example

Relevant excerpt from above:

class AlbumController
{ 
    public function getAlbumTable()
    {
        if (!$this->albumTable) {
            $sm = $this->getServiceLocator();

            //* My Question Lies Here: 
            $this->albumTable = $sm->get('Album\Model\AlbumTable');
        }
        return $this->albumTable;
    }
}

What is $this->albumTable? Its type is AlbumTable. What is AlbumTable? It's a class that manages a an instance of class TableGateway of ZendDb module of ZF2.

The problem

In a way, AlbumController depends on TableGateway. ServiceLocator just makes this chain of dependencies slightly more hidden away.

It actually makes $this->albumTable seem like a "variable that contains data" and adds ability to "call that variable from anywhere inside the code on a whim" -- kind of like declaring an int data type, you declare TableGateway that is tied to your own entity, along with ability to add your own custom business data access and retrieval functions. I concede that it is pretty cool ...

So? What's my problem with it?

Problem: class AlbumController contains dependency on TableGateway.

I wanted to seek out more clarification on why this is acceptable in terms of Single Responsibility Principle and other similar OO separation concerns. Perhaps I am taking it a bit far, but all AlbumController needs is the data that TableGateway can provide -- namely things like data of Album() type. Why not implement a mechanism that can get AlbumController its data but without the indirect dependency on TableGateway? There are design patterns that exist that can make that happen.

To Summarize

  • Why tolerate dependency on TableGateway inside your Controller class?
  • How did ServiceLocator come about -- it is seemingly used to "put that dependency as far away as possible while still keeping it there". Is that the purpose of SeviceLocator?
  • Why not use some other OO facilities (Factories, Builders, etc) to provide/populate Controller directly with data it needs (i.e. Album), instead of weighing it down with TableGateway?

Spirit of the Question

What is the purpose of ServiceLocator Pattern? Why is it there why is it needed what problem does it really solve, when it seems to just push things off dependency-wise

Also, ServiceLocator is a dependency in every ZF2 Controller that extends AbstractController... Thus making our custom AlbumController depend on both ServiceLocator and anything that ServiceLocator decides to call (i.e a thing like TableGateway). You can thus call any Service that you care to out of any Controller place you wish. What happened to dependency injection and inversion of control concepts?

UPDATE:

It seems like Zend has deprecated ServiceManager::getServiceLocator() as of v3.0.0. Recommends to "use the container passed to the factory instead".

See also this blog post on deprecating ServiceLocatorAware

9
  • 4
    The service locator is an anti-pattern. Sadly, lots of software, including Zend, was written before this fact started being widely recognised. DI and IoC are the modern way of doing things. They have consigned singletons and service locators to the dustbin of history, to be picked over by developer dinosaurs. Sadly this leaves you with two choices: accept Zend is far from perfect, but live with it as a way to achieve what you need; or walk away from Zend.
    – David Arno
    Oct 9, 2015 at 20:41
  • 2
    hmmm ... good thought. The way I am using Zend though is not as a full system but using its select modules. I have not been using ServiceLocator, but started looking into it and had this issue. I guess I can just avoid using it.
    – Dennis
    Oct 9, 2015 at 21:04
  • 1
    @david "anti-pattern" is an empty way of saying "a pattern I don't like" without having to discuss why. There are legitimate uses for service locator-like patterns...
    – svidgen
    Oct 10, 2015 at 14:34
  • 1
    @david ... Strange. I use a service locator, and my test suite works quite well! (But yes. That's the dogma I'd expect to hear..)
    – svidgen
    Oct 10, 2015 at 17:56
  • 1
    @DeadMG A service locator doesn't imply a global state any more than DI does. Nothing prevents you from injecting a global. A service locator also doesn't necessarily hide anything DI doesn't hide. To the contrary, they're both mechanisms that exist for the purpose of hiding things -- things outside the module, which may or may not be global or have other dependencies. It's a difference of whether the module receives certain things from some unknown source at invocation time or gets them from a known source as-needed (a source which you're free to injected, mind you).
    – svidgen
    Jun 6, 2016 at 19:01

2 Answers 2

0

The Symfony 2 framework documentation share the same issue in that most of the official examples use a service locator. This is done because a service locator is a bit easier to demonstrate then dependency injection, especially to new comers.

The documentation for both frameworks also show multiple actions within a single controller class. Again, one class is easier to explain and organize than multiple classes. Of course if only one action required a specific service then using constructor injection means that the service is constructed for all actions. Service locators might actually be a better fit for this approach especially if you keep the action methods thin to work around testing issues.

However, both Zend and Symfony fully support dependency injection. http://framework.zend.com/manual/current/en/in-depth-guide/services-and-servicemanager.html.

And while I have not used Zend in production, for Symfony 2 I have adopted the approach defining all controllers as services and of only allowing one action per controller class which eliminates the issue of injecting unneeded dependencies.

In this particular case one needs to look past the introductory documentation to see what the frameworks are really capable of.

0

Including a Service Locator into a Controller or a Service is indeed an anti-pattern, no longer used since ZF version 3. Instead, use Factories and Container, such as ZF3's factory-based Service Manager.

Anti-pattern, because, passing $container to an action or controller makes your code more complicated. It makes your controller/action depend on entirety of classes that $container has accumulated so far. You have no idea what controller/action will use the $container for, and therefore you are hiding dependencies by doing so. An approach below instead exposes dependencies and explicitly points them out via passing them to the __construct method of controller/action.

Currently recommended form is below, where $sm is now $container, which could be an instance of ZF3's ServiceManager:

/* Invoked via ZF3 configuration */
class AlbumControllerFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $albumTable = $container->get(AlbumTable::class);
        return new AlbumController($albumTable);
    }
}

class AlbumController
{    
    /** @var AlbumTable */
    private $albumTable;

    /*
     * Here we inject dependency of AlbumTable
     * into AlbumController
     * We are telling AlbumController exactly what to use (AlbumTable)
     * We do not let AlbumController decide what it wants to use
     */
    public function __construct(AlbumTable $albumTable)
    {
        $this->albumTable = $albumTable;
    }

    /*
     * Example use of $albumTable
     */
    public function indexAction()
    {
        return new ViewModel(array(
            'albums' => $this->albumTable->fetchAll()
        ));
    }
}

If you have a need for lazy instantiation of AlbumTable's methods, take a look at Lazy Services of ZF3's ServiceManager.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.