Beyond Dependency Injection

10/18/17

Yes, another blog about Dependency Injection and how to put A as a dependency into B. It's so easy with the help of frameworks and DI containers to build a class structure and let them be automagically instantiated in your projects, so why creating another one? Because dependency doesn't end after the injection.

What is Dependency Injection?


There will be always a new generation of developers, so we have to cover that question first. Let's take a look at this useful little PHP function.
<?php

function square_of_2() : int
{
    return 2 * 2;
}
A simple function that calculates the square of 2 and is easy to copy and paste in case we need to get the square value of other numbers.
<?php

function square_of_2() : int
{
    return 2 * 2;
}

function square_of_6() : int
{
    return 6 * 6;
}

function square_of_8() : int
{
    return 8 * 8;
}
As you can see, this is pretty repetitive and when you think about it, there are an infinitive amount of numbers you may need to calculate the same way. Would you always create a new function for that?

The only difference between those functions is the number you'll need for the calculation, so what about being able to make that number replaceable?
<?php

function square(int $number) : int
{
    return $number * $number;
}
By injecting the number as an argument, we don't need to create multiple functions for every calculation. Copy and paste is a source of bugs and duplicated code anyway.

"Wait, that's not Dependency Injection" you say? That's true, you won't find functions in DI examples because they have no state, right? Well, some PHP functions, like date(), have a dependency to the PHP configuration or a library, so is that an injected dependency too? At least it's one you can't inject yourself, so we cannot speak about "injection" here, but I digress. Let's go to classes and objects.

With Dependency Injection you can inject class instances into another object and use them inside that dependent object. Or in simple terms, you're putting object A into object B, so that B can use the functionality of A.
<?php

class Config
{
    private $reader;
    
    public function __construct(ReaderInterface $reader)
    {
        $this->reader = $reader;
    }
    
    public function doSomethingWithConfig($configFile)
    {
        $fileContent = $this->reader->readFile($configFile);
        // Whatever this class was meant to do
    }
}

// All 3 implement ReaderInterface
$xmlReader = new XmlReader();
$jsonReader = new JsonReader();
$yamlReader = new YamlReader();

$config1 = new Config($xmlReader);
$config2 = new Config($jsonReader);
$config3 = new Config($yamlReader);
In this example, we created a class that handles configuration values from different config formats like XML, JSON and YAML.

As we learned from the examples of the square functions, we can make code simpler when we keep the common code and make the variable parts injectable. This is also a good way to separate concerns. The only difference is the configuration format, while the rest can be handled by Config itself. We created 3 instances for Config but every one has a different state.

An interface isn't necessary, but with Dependency Injection comes a contract between objects and interfaces allow you to specify a dependency, that doesn't belong to a specific implementation, only its API.

Is there more about DI?


"That's pretty obvious" will come into your mind, but when I look back into the past years of OOP projects, there were many repetitive classes similar to the function examples before. With a higher complexity you can't always catch design flaws that easy and even experienced developers can have a hard time building up their class structures. A customer's project code doesn't need to be as generic as a framework, which is why our effort as developers goes straight into the business logic or other more specific implementations.

A dependent class' needs


When we go back to the definition of Dependency Injection, it doesn't explain anything about a dependency structure, it just tells you to put one object into another. So by definition, this is also Dependency injection:
<?php

class Car
{
    private $gasStation;

    public function __construct(GasStation $gasStation)
    {
        $this->gasStation = $gasStation;
    }
}
We need gasoline for a car, right? Where do we get gasoline? From a gas station of course, so a car has a dependency to a gas station, right?

A car needs many things that can be bought at a gas station, but at the moment, I want to focus on gasoline only for my example. Somewhere inside the GasStation instance, there must be a method that can give us some fuel for our car, but as you know, there are other ways to fill up a car. Theoretically, you can even use your bare hands to fill in the gasoline little by little. Currently, we can only use a gas station, so our car class example isn't really close to a real world car's dependency. You should know where this is going to by now: Our real dependency to Car is gasoline.
<?php

class Car
{
    private $gasoline;

    public function __construct(Gasoline $gasoline)
    {
        $this->gasoline = $gasoline;
    }
}
I'll use a Gasoline object to make the examples more readable. In reality, the code for Car would be more complex.

Another way to inject a dependency, next to a constructor, is to use a setter method. Setter are usually used for mutable value objects and are considered a bad practice for services because a dependency shouldn't be omitted on object creation. If you keep a dependency optional, you would need to check for an instance every time before you use a method of your dependency.
<?php

class Car
{
    private $gasoline;

    public function setGasoline(Gasoline $gasoline)
    {
        $this->gasoline = $gasoline;
    }
    
    public function drive()
    {
        // ... some code
        
        // Error if $this->gasoline is null
        $this->gasoline->reduceBy($number)
        
        // ... some code
    }
}
We would be forced to add if-blocks for every call on the gasoline methods.

We need to be able to refuel our car during its runtime, assuming that an existing method Car::drive() can drain our gasoline. But no one would ever say "I need to go and set gasoline into my car". Neither do we "add" gasoline. We fill up.
It also doesn't make any sense that a car needs gasoline on it's creation, so in the next example a gas tank was added, that can be filled up with gasoline later.
<?php

class Car
{
    private $gasTank;
    
    public function __construct(GasTank $gasTank)
    {
        $this->gasTank = $gasTank;
    }
    
    // Methods like drive()

    // Instead setGasoline() or addGasoline()
    public function fillUp(Gasoline $gasoline)
    {
        $this->gasTank->add($gasoline);
    }
}
You can enhance these examples by adding a more generic "gas tank" that can allow Car to be an electric car, while Gasoline can simply be another form of fuel now. This will be the second time where we have a change of dependency in our car example. Car::setGasoline() or Car::addGasoline() wouldn't be a good name for an electric car anymore and changing the API of class' methods can take some time in a big project. As you can see, setter injection (or "add") can also come in different names. Despite the naming, which can make code easier to read, the contract between a car and its fuel stays the same.

Going further with a contract


Enough with cars, let's stay with the topic of a contract between two classes/objects. This time with a more generic example.
<?php

class Foo
{
    public function doFoo(array $foo) : array
    {
        // work with array $foo
    }
}

class Bar
{
    private $foo;

    // ... Inject Foo instance in constructor.
    
    public function doBar() : array
    {
        $array = [
            'id' =>    3,
            'name' => 'bar',
            'amount' => 4,
        ];
        
        $fooArray = $this->foo->doFoo($array);
        
        return $fooArray;
    }
}
Here, we have a class Bar that has a dependency to Foo. It uses a method called Foo::doFoo() on its dependency to let it do some work with an associative array it provides. Bar doesn't and shouldn't care what Foo::doFoo() does inside its method body, it only needs to know that doFoo needs an array as an argument and that it returns an array. Everything inside the method is supposed to be a black box. But if we take a look inside doFoo(), we will realize that there can be more dependencies between classes than just the contract of type hints and interfaces.
<?php

class Foo
{
    /**
     * Possible array injected by Bar
     *
     * [
     *     'id' => 3,
     *     'name' => 'bar',
     *     'amount' => 4,
     * ]
     */
    public function doFoo(array $foo) : array
    {
        $id = array_shift($foo);
        
        // Do stuff with $foo and return a new array
    }
}
If you take a close look at the example above, we now have a new dependency between Bar and Foo: A dependency to the array's order of elements. The example expects the first element to be the id, but how should Bar know that? If it changes the order, Foo::doFoo() will not recognize it and stop working as expected. In case that the element name becomes first, you may get notices,warnings or a fatal error depending on what your code is doing with the values. But in case of amount it can work, silently creating inconsistent data, that may only become visible, for example, in form of foreign key constraint errors of a database one day. Unit tests won't find such an issue, which is why integration tests are a must have for a bigger project. Even if you use the array key to access a value, you can either assume, that an array key exists or use a check for it and implement a logic, that handles missing values. It doesn't matter, you decide between error prone code or more complex code, that needs more tests. With fixed accessors like id, name and amount you can also choose and prefer to create a value object with an interface containing getter methods instead of an array and introduce a much clearer contract.

Again, I used a simple code example and maybe made the array type look like an anti-pattern, but it isn't about arrays. It is about hidden dependencies between two objects, that still can happen even after proper Dependency Injection was achieved. It's about misappropriation of types and values, that can hide bugs in your code.

In the end, it starts with DI


Dependency Injection is an important basic and not the end for mastering new patterns. It can make complexity more maintainable, but not less error prone or easier to read, if you don't take any thoughts into your code. This is why even a test coverage of 100% can contain uncovered use cases and paths leading buggy results, where the worst bugs are inconsistent data states. Those are harder to debug than the ones throwing an exception or error.
But it can separate your code to smaller units and make them easier to test. Even though we don't write code for reusability, Dependency Injection can make a project more flexible with all its parts, being a good strategy for agile development. After starting to use it, you'll have to gain experience and learn how you can make projects easier to handle and therefore your life as a developer more comfortable.


About Claudio Zizza
I'm working as a software developer for more than 15 years and used many coding languages and frameworks during that time, like PHP (of course), ASP, .NET, Javascript, Symfony, Laminas and more. I also contribute to the open source communities and am a part of the PHP Usergroup in Karlsruhe, Germany.
Follow me on Twitter (@SenseException)