There are different patterns that allow us to inject dependencies into the classes in order to create loosely-coupled code.
Among them I will discuss about the most popular ones:
- the Service Locator pattern
- the Factory pattern
- Constructor/setter injection, Composition Root and DIC
These patterns are used as a replacement for the new operation in a class: instantiating a class inside another class make these classes tightly-coupled. But they also avoid the need to create Singletons for those objects that are used application-wide.
Service Locator pattern
The Service Locator is a well-known object (by well-known I mean global like a singleton or just passed to most objects) that allows you to register common and widely-used objects (for example model instances, config instance, etc.). Other objects can then use the service locator to get those registered objects, called services.
In your code, you can then call services dynamically like:
$someService = MyServiceLocator::getInstance()->get('someService');
It is generally considered an anti-pattern, mainly for reasons that concern compiled languages:
The main reason is that you are more prone to run-time errors than compile-time errors:
If you forget to register a service in your Locator, you will get an exception at run-time. You need to know the entire application to see where the Service Locator is being used and registering its services. The compiler won’t help you with that.
Another point is that a Service Locator returns a completely generic object, so you could retrieve the service in the wrong type of class without any compile-time error.
As the drawbacks of the Service Locator pattern concern mostly compiled language, is it okay to use it in PHP applications ?
Well, I don’t think so. Even though PHP isn’t a compiled language, debugging errors can reveal itself quite hard if you used an application-wide Service Locator.
The Service Locator is hiding the dependencies throughout the code, and makes the API unclear. As dependencies are hided in the code, this would require another programmer to look through all the code to understand how it works.
And if for some reason you need to change a service, where will that change need to be done? You used your Service Locator application-wide.
Beside that, there are still some other reasons why you wouldn’t want to use the Service Locator:
Unit testing is harder to accomplish.
The Factory pattern also allows you to inject dependencies in your code and is very similar to the Service Locator. The main difference resides in the fact that a Factory, when it is well implemented, can return instances only of a specific type, whereas the Service Locator returns a completely generic type.
Constructor/setter injection and Composition Root
Of course, you could just inject the dependencies into the constructors of your classes, and this is considered as the best approach.
But this is not as easy as it sounds:
As we push the point of creation of the dependencies out of the class, we end up giving the responsibility of creating the object to another class. We will then quickly realize that we will have to push the creations upwards.
In a PHP MVC framework, we will push these up until we meet the object that creates our controller entry point, usually this is done in a Command Dispatcher object or a bootstrap file. This place is called the Composition Root.
It is at that place that all the dependencies will be defined, and in most cases you want a framework that instantiates and sets up your dependencies for you by the means of a configuration file. That framework is called DIC or Dependency Injection Container.