Home > Keeping up with the dependencies
Keeping up with the dependencies

Keeping up with the dependencies

Service locator (SL) and dependency injection (DI) design patterns serve the same purpose. To make your code loosely coupled to have a better testable, maintainable, and extendable code. Both these patterns are implementing the inverse of control pattern. We use them to handle the dependencies between multiple classes.

Service locator and dependency injection container (DiC) are trying to handle dependencies of your object in one place. The difference is that with SL you are defining the classes (services) in one place and you are letting the classes take care of their dependencies. Whereas with DiC you are providing the dependencies to the classes.

Tightly/loosely coupled code

We’ll explain the code coupling in a simple example. Say we have a class that needs to interact with some kind of a database (the most usual example you will find out in the wild). We have a Redis database and the code looks like this (simplified)

<?php class RedisDb { public function get(string $key) : string {/* ... */} }
Code language: HTML, XML (xml)

.Redis is not a traditional a database like PostgreSQL or MySQL, but it can be used for persisting some data in-memory.

The class that needs to interact with this could then look like this

<?php class ExampleService { public function executeCode() { $redisInstance = new RedisDb(); $data = $redisInstance->get('someDataKey'); // Business logic goes here. } }
Code language: HTML, XML (xml)

While the above is a valid code, it’s tightly coupled. That means that the ExampleService class has a direct dependency on the RedisDb class. Because it instantiated it in the public method. If you make some changes to the RedisDb class, you’ll need to make sure you didn’t make any breaking change in it. That would directly impact the ExampleService class. Extending it is also hard, because you need to take care of the added dependency (RedisDb). Plus, such classes are really hard to test. We’ll either need to provide the RedisDb class in the tests, or mock it and override the RedisDb in the tests with our mock. Which is often times very difficult and time consuming.

One way to get rid of this tight coupling is to inject our RedisDb into the ExampleService class using constructor injection.

<?php class ExampleService { private $redisInstance; public function __construct(RedisDb $redisInstance) { $this->redisInstance = $redisInstance; } public function executeCode() { $data = $this->redisInstance->get('someDataKey'); // Business logic goes here. } }
Code language: HTML, XML (xml)

This way we are passing the RedisDb as a dependency into our ExampleService class, making it less coupled. This is ok, but we can do better.

Dependency inversion

One of the principles of OOP is the dependency inversion principle (one of the SOLID principles). It’s often described as: depend on abstractions, not concrete implementations. For that we need interfaces. Interfaces are contracts – they are only describing what public methods our classes need to implement. They won’t contain any business logic. That is left for the class that implements the interface to do (to honor the contract set by the interface). We also mentioned public methods, because those are the methods exposed to the world (our app).

In our example we would create a Database interface

<?php interface Database { public function get(string $key) : string; }
Code language: HTML, XML (xml)

Now we can implement this for many different databases: MySQL, PostgreSQL, Redis etc.

<?php class RedisDb implements Database { public function get(string $key): string {/* concrete implementation */} }
Code language: HTML, XML (xml)
<?php class ExampleService { private $database; public function __construct(Database $database) { $this->database = $database; } public function executeCode() { $data = $this->database->get('someDataKey'); // Business logic goes here. } }
Code language: HTML, XML (xml)

Notice how we used Database as an injector type hint in the ExampleService constructor. We no longer depend on the concrete implementation. We can easily replace this with a different database at any point in time. This is the example of loosely coupled code. This is something we can easily test, as the provided instance can be a mock implementing the Database interface.

Service locator drawback

Service locator is basically a container containing all the dependencies in one place. We can then inject this container in our service and use whatever dependency we need from it. An example would be

<?php class ExampleService { private $container; public function __construct(ContainerInterface $container) { $this->container = $container; } public function executeCode() { $data = $this->container->useDb('database')->get('someDataKey'); } }
Code language: HTML, XML (xml)

While this sounds fine, it’s problematic because we don’t know what is the actual dependency of our class.
We only know that our Service class depends on the ContainerInterface, but apart from that, we don’t know precisely which one it is. We only know that it needs to work with a database in the executeCode method. In contrast to this, when we used dependency injection in the ExampleService, we knew exactly that it depends on the Database interface (it expects the database).

While in a sense you are decoupling your code like with dependency injection, you are obfuscating the dependency graph. This can make it harder for you to debug errors in case anything breaks when the code changes, and it may make the system difficult to maintain.

Dependency injection container

In our WordPress projects we recently started implementing the dependency injection container using the php-di library. This library is great because it offers automatic autowiring of the dependencies. Autowiring is the ability of the container to automatically create and inject dependencies.

WordPress is a bit specific because it works with hooks (actions and filters, not React hooks). So to initialize the class we need to register our actions and filters that are located in the class. For this we use a register() method as a place for the hooks. At our plugin initial point we add a list of classes in the get_service_classes() method, which returns an array of fully qualified class names

<?php private function get_service_classes() : array { return [ Admin\Optimizations::class, Users\User_Manager::class, ... ]; }
Code language: HTML, XML (xml)

Then we initialize the DI container and kickstart our WordPress classes like this

<?php use \DI\ContainerBuilder; ... final class Plugin implements Registerable, Has_Activation, Has_Deactivation { private $services = []; public function register_services() { // Bail early so we don't instantiate services twice. if ( ! empty( $this->services ) ) { return; } $builder = new ContainerBuilder(); $container = $builder->build(); $this->services = array_map( function( $class ) use ( $container ) { return $container->get( $class ); }, $this->get_service_classes() ); array_walk( $this->services, function( $class ) { if ( ! $class instanceof Service ) { throw Exception\Invalid_Service::from_service( $class ); } $class->register(); } ); } }
Code language: PHP (php)

This way we first autowired our classes, and then we pull the classes with the register() method and then we invoke this method. The register_services() method hooks to plugins_loaded action in our plugin, and this way all our hooks are properly invoked when needed.


Working with real life applications means that your code will get complicated. Parts of the code will need to interact one with another, perfect separation is an ideal that we can strive to, but will most probably never achieve. That is why we are dependent on things like dependency injection (no pun intended).

While DI can be a bit complicated to learn at first, it’s benefits are numerous. It will help you with the unit testing your code. Reduce the boilerplate code. Extending the application becomes easier (like in the database example) and it loosens the code coupling. So go ahead and give it a try if you are not using it already.


4 responses to “Keeping up with the dependencies”

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.