Design Patterns – Explain the ServiceLocator Pattern in Zend Framework 2

design-patternsPHPsingle-responsibilityzend-framework

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

Best Answer

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.

Related Topic