Keeping up with the dependencies

Dependency injection container image with two boxes representing classes and a cog representing a dependency injection container

In object oriented programming (OOP) we are dealing with classes as the main 'unit' of our code. Classes represent the blueprints to the objects that will have some kind of business logic inside of it. Often times our classes need to interact with one another. This is when the coupling of the code happens. How do we handle this coupling? There are a few design patterns that can help with that.

Service locator (SL) and dependency injection (DI) design patterns serve the same purpose: to make your code loosely coupled in order to have a better testable, maintainable and extendable code. Both these patterns are implementing the inverse of control pattern, and are used 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 explicitly providing the dependencies to the classes directly.

Tightly/loosely coupled code

We’ll explain the code coupling in a simple example. For instance, 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). Say we have a Redis database (Redis is not a traditional a database like PostgreSQL or MySQL, but it can be used for persisting some data in-memory) and the code looks like this (simplified)

<?php

class RedisDb {
  public function get(string $key) : string {/* ... */}
}

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.
  }
}

While the above is a valid code, it’s tightly coupled. Meaning 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, because 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.
  }
}

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

One of the principles of OOP is called 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;
}

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 */}
}
<?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.
  }
}

Notice how we used Database as an injector type hint in the ExampleService constructor. We no longer depend on the concrete implementation, but can easily replace this with a different database at any point in time. We now have a 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');
  }
}

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, which 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 if 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,
      ...
    ];
}

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();
      }
    );
  }
}

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 is hooked to plugins_loaded action in our plugin, and this way all our hooks are properly invoked when needed.

Conclusion

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 unit testing your code, it will 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.

Join the Discussion