Blog/ Dependency Injection

If you are starting to learn about Drupal 8, you must have come across a term called "Dependency Injection". If you are wondering what it means, then this is the post you should read. Through examples, you will learn why dependency injection is useful for decoupling the code as well as unit testing effectively.

In the post on PHP Inheritance, we created HondaAccord and ChevyRam classes, both of which extend the Vehicle class. Vehicle has getWarrantyLeft(),drive() and getMilesDriven() methods. getWarrantyLeft() method is being overridden by ChevyRam while HondaAccord has a trunk and implements putInTrunk() and takeFromTrunk() methods. Vehicle also has color, warranty and milesDriven properties.

Let's also assume that there is a Person class and each person owns a vehicle. We are initializing this vehicle in the constructor of the Person class. Person has methods travel() and getDistanceTraveled(). travel() method uses Vehicle's drive() method and getDistanceTraveled() method uses the Vehicle's getMilesDriven() method. Here is how Vehicle.php and Person.php files look:

You will notice that in the constructor of Person class, we are initializing the vehicle he owns to be a red Honda Accord. So this code works fine as long as we expect all the people to own a red Honda Accord. But obviously this is not the case in real life. In Inheritance in PHP post, we had a case where I owned a red Chevy Ram truck while you owned a white Honda Accord. So how do we initialize two people, you and me, such that we both own different vehicles? The easiest way is to remove the initialization of the vehicle from the Person class. Instead initialize it outside and pass the initialized Vehicle class to the Person class in the constructor. This is how the file Person.php will look after this change:

What we did above is that we initialized the Vehicle object outside the Person class and passed the Vehicle object to the Person class when initializing a person. Earlier since Vehicleobject was initialized within the Person class, Person class had a hard dependency on the Vehicle object. Now that we are passing an initialized Vehicle object to the Person class, the dependency has been decoupled to an extent. We can initialize any type of Vehicle and pass it to the Person being initialized. The Person class doesn't really care what type of Vehicle object is being passed as long as it implements drive() and getMilesDriven() methods. This is dependency injection. Earlier Person class was dependent on the Vehicle class but now we are initializing the Vehicle object elsewhere and injecting it into the Person object. If you understood what we did here, you understand what dependency injection is. Now let's understand the benefits of dependency injection.

Benefits

Decoupled code, which leads to more flexible architecture

The reason we used dependency injection in the above example is to increase the flexibility. Before using dependency injection, every person was initialized to own a red Honda Accord. After using dependency injection, every person will get initialized to own any vehicle that you initialize, and not necessary a red Honda Accord. In the example above, $me was initialized with a red Chevy Ram and $you were initialized with a white Honda Accord. Now let's take it one step further and instead of defining the argument in the constructor to be an object of the type Vehicle, define it to be an object that implements VehicleInterface. Since a person is using only two methods, drive() and getMilesDriven(), of the Vehicle object, we will define these methods to be in the interface and let the person be initialized with any object that implements this interface. An example of such an object could be a bullock cart. It has drive()and getMilesDriven() methods but doesn't have any other method, such as getWarrantyLeft(). Here's how VehicleInterface.php, BullockCart.php and Person.php will look in such a case:

As seen in the code above, Person object is no longer dependent on the Vehicle object. In fact, we can even initialize it using the BullockCart object, which is not related to the Vehicle object in any way, as long as BullockCart object implements VehicleInterface, which in turn requires only two methods: drive() and getMilesDriven(). We have truly made Person and Vehicleclasses decoupled and increased the flexibility of the code.

Easier Unit Testing

The second advantage of dependency injection is the ability to unit test the classes Vehicleand Person independent of each other. Consider the initial code when Person was initializing the Vehicle object in its constructor. This is how a PHPUnit test for Person will look:

The above test will pass. Let's assume that in future, there is a mistake in the implementation of getMilesDriven() in the Vehicle class. Suppose getMilesDriven() function in Vehicle class starts returning 0 irrespective of the miles actually driven. Now the above test will fail since getDistanceTraveled() method, which in turn calls Vehicle's getMilesDriven() method, returns 0 and does not match 1000. This is undesirable. Why should a test testing Person class fail if there is a mistake in Vehicle class? Agreed that in the above code, it's very easy to debug what's going on but when the project grows, just figuring the root cause of a failing test can become time-consuming. What we want is that a unit test testing the Person class should fail if and only if there is a problem in the Person class and not anywhere else. This is where dependency injection is useful. Now that we are creating an object implementing VehicleInterface and passing it in the Person's constructor, we can use mocks in PHPUnit to isolate the tests for Person class from any other class. Here is how the PHPUnit test for Person class will look after implementing dependency injection using VehicleInterface:

The test code above is slightly more complicated that the one earlier. First we are creating a mock for VehicleInterface and instructing the mock to return 1000 whenever its method getMilesDriven() is called. Next we pass this mock to the Person object during initialization. As a result, In the assertEquals() statement, $this->getDistanceTraveled() will invoke getMilesDriven() method of the mock and will return 1000 as programmed. You will notice that even in the unit test, we have eliminated the dependency on any external class. Even if the implementation of getMilesDriven() method in the Vehicle or BullockCart class has bugs, the above test will pass since we are using a mock that gets passed to the Person object. So in future, if the above test fails, then we know for sure that there is a problem in the Personclass and nowhere else. It becomes much easier to debug.

In this post, you learned about dependency injection and its benefits. In the next post, you'll learn about dependency injection container. In the meantime, please comment below if you can think of any other advantages of using dependency injection. If you found some part of this post difficult to understand, please let me know in a comment below so that I can make it easier to understand.

Comments

I'm not getting the very last example in the dependency injection, right before the unit tests. You mention "As seen in the code above, Person object is no longer dependent on the Vehicle object. " But the example code clearly shows "function __construct(Vehicle $vehicle) {". Is there a typo?