Dependency Injection Container Part 2

Here we go again. Building a DI-Container with Reflection, making use of autowiring, for constructor and setter injection.

In dependency injection the term autowiring is used if your dependencies / objects are wired in an automatic way. Take a look at the following example

class Request
{
}
class Response
{
    private $request;
    public function __construct(Request $request)
    {
        $this->request = $request;
    }
}
class Router
{
    private $request;
    private $response;
    public function __construct(Request $request, Response $response)
    {
        $this->request = $request;
        $this->response = $response;
    }
}

To wire the objects you would need something like this

$request = new Request();
$response = new Response($request);
$router = new Router($request, $response);

With autowiring you would do something like this

$router = new Injector('Router');

You might be curious why I do not use something like this:

$router = new Injector(new Router());

That is because Router requires Request and Response and hence both would need to be initiated beforehand, which would then make autowiring using constructor injection more or less useless. At least I haven’t found a nice way to achieve that. Anyway. Back to the example. Injector would take care of initializing Response, Request and so on. This fulfills the Hollywood-principle as in: Don’t call us – we’ll call you. There is no code in Router which asks Injector to inject Request or Response. There is no code in Router which would make Router depend on the Injector.

building an example implementation

So, how does the injector know about the constructor of Router? There are several ways to achieve this. For one you could follow a build-procedure in which you’d scan all your files and create a loader-file which would then just initialize the objects. As you would do this only once, this should be pretty fast. The alternative is using Reflection:

class Injector
{
    public function register($class)
    {
        $reflectionClass = new \ReflectionClass($class);
        $reflectionConstructor = $reflectionClass->getConstructor();
    }
}

You can easily grab it’s parameters as well, like so

class Injector
{
    public function register($class)
    {
        $reflectionClass = new \ReflectionClass($class);
        $reflectionConstructor = $reflectionClass->getConstructor();
        $reflectionConstructorParameters = $reflectionConstructor->getParameters();
        foreach($reflectionConstructorParameters as $reflectionParameter)
        {
            .. do something
        }
    }
}

With a constructor like public function __construct(Request $request, Response $response) you would only get $request and $response at least in PHP 5.5.9 which is what I am using. For constructor injection we do need the type i.e. Request and Response. Currently I am using preg_match to parse the string representation of the parameter – I haven’t seen another way, yet.

class Injector
{
    public function register($class)
    {
        $reflectionClass = new \ReflectionClass($class);
        $reflectionConstructor = $reflectionClass->getConstructor();
        $reflectionConstructorParameters = $reflectionConstructor->getParameters();
        foreach($reflectionConstructorParameters as $reflectionParameter)
        {
            preg_match('/^Parameter #\d+ \[ <\w+> (?P<class>[A-Za-z0-9\\\\_]+)'
                      .' \$[A-Za-z0-9_]+ \]$/is', $reflectionParameter, $matches);
            if (isset($matches['class'])) {
                $parameters[] = $matches['class'];
            }
        }
        var_dump($parameters);
    }
}
 
$inj = new Injector();
$inj->register('Router');

In this example parameters will result in an array containing the types:

array(2) {
  [0]=>
  string(7) "Request"
  [1]=>
  string(8) "Response"
}

We’re now aware of that we have to inject the Request and Response objects into Router using constructor injection. The same approach will work if you do exchange $reflectionConstructor = $reflectionClass->getConstructor(); with $reflectionMethods = $reflectionClass->getMethods(); and iterate through the methods of the class. Which will be handy for setter injection.

Now let us turn Injector into a container by modifying it so that it stores initialized objects which can later be injected.

class Injector
{
    private $instances;
    public function register($class)
    {
        $reflectionClass = new \ReflectionClass($class);
        $reflectionConstructor = $reflectionClass->getConstructor();
        if(!is_null($reflectionConstructor))
        {
            $reflectionConstructorParameters = $reflectionConstructor->getParameters();
            foreach($reflectionConstructorParameters as $reflectionParameter)
            {
                preg_match('/^Parameter #\d+ \[ <\w+> (?P<class>[A-Za-z0-9\\\\_]+)'
                         .' \$[A-Za-z0-9_]+ \]$/is', $reflectionParameter, $matches);
                if (isset($matches['class'])) {
                    $parameters[] = $this->register($matches['class']);
                }
            }
            $this->instances[$reflectionClass->name] = $reflectionClass->newInstanceArgs($parameters);
        } else {
            $this->instances[$reflectionClass->name] = $reflectionClass->newInstance();
        }
    }
 
    return $this->instances[$reflectionClass->name];
}
 
$inj = new Injector();
$router = $inj->register('Router');
var_dump($router);

which will result in

object(Router)#9 (2) {
  ["request":"Router":private]=>
  object(Request)#7 (0) {
  }
  ["response":"Router":private]=>
  object(Response)#10 (1) {
    ["request":"Response":private]=>
    object(Request)#11 (0) {
    }
  }
}

Noticed that this did not only inject Request and Response into Router, it did also inject Request into the Response object? In my opinion autowiring with dependency injection is mostly used for objects which are shared among other objects. You can see that in the above example there are two instances of Request (#7 and #11) we can make it shared with a small modification:

class Injector
{
    private $instances;
    public function register($class)
    {
        $reflectionClass = new \ReflectionClass($class);
        if(!isset($this->instances[$reflectionClass->name]))
        {
            $reflectionConstructor = $reflectionClass->getConstructor();
            if(!is_null($reflectionConstructor))
            {
                $reflectionConstructorParameters = $reflectionConstructor->getParameters();
                foreach($reflectionConstructorParameters as $reflectionParameter)
                {
                    preg_match('/^Parameter #\d+ \[ <\w+> (?P<class>[A-Za-z0-9\\\\_]+)'
                            .' \$[A-Za-z0-9_]+ \]$/is', $reflectionParameter, $matches);
                    if (isset($matches['class'])) {
                        $parameters[] = $this->register($matches['class']);
                    }
                }
                $this->instances[$reflectionClass->name] = $reflectionClass->newInstanceArgs($parameters);
            } else {
                $this->instances[$reflectionClass->name] = $reflectionClass->newInstance();
            }
        }
 
        return $this->instances[$reflectionClass->name];
    }
}
 
$inj = new Injector();
$router = $inj->register('Router');
var_dump($router);

and we get

object(Router)#9 (2) {
  ["request":"Router":private]=>
  object(Request)#7 (0) {
  }
  ["response":"Router":private]=>
  object(Response)#10 (1) {
    ["request":"Response":private]=>
    object(Request)#7 (0) {
    }
  }
}

here you can see that the request object is shared (#7 and #7). Now there are a few problems. First of all, the reflection object will be re-created over and over again if you do share an object multiple times. We’ll solve that by adding another property so that the reflection object is only initiated once. The second problem is that there are no checks if for example the class is initiable. The third problem might be my assumption that you do want objects shared if you are using autowiring together with dependency injection. Next one: is my regular expression correct?

I am modifying the first part of Injector so that reflected classes will be there only once.

class Injector
{
    private $reflected = array();
    private $instances = array();
    public function register($class)
    {
        if(!isset($this->reflected[$class]))
        {
            $this->reflected[$class] = new \ReflectionClass($class);
        }
 
        $reflectionClass = $this->reflected[$class];

Then I did add a check to see if the object is instantiable.

        if ($reflectionClass instanceof \ReflectionClass && $reflectionClass->isInstantiable())
        {
            if(!isset($this->instances[$reflectionClass->name]))
            {
                $reflectionConstructor = $reflectionClass->getConstructor();
                if(!is_null($reflectionConstructor))
                {

Then I made sure, that the regular expression for detecting the class name matches the one of the documentation just adding \\\\ to the list, so that namespaces will work.

                    $reflectionConstructorParameters = $reflectionConstructor->getParameters();
                    foreach($reflectionConstructorParameters as $reflectionParameter)
                    {
                        preg_match('/^Parameter #\d+ \[ <\w+>'
                                 .' (?P<class>[a-zA-Z_\x7f-\xff][a-zA-Z0-9\\\\_\x7f-\xff]*)'
                                 .' \$[A-Za-z0-9_]+ \]$/is', $reflectionParameter, $matches);

Furthermore there is no need to call $this->register if the class is already initiated.

                        if (isset($matches['class'])) {
                            if(isset($this->instances[$matches['class']])){
                                $parameters[] = $this->instances[$matches['class']];
                            } else {
                                $parameters[] = $this->register($matches['class']);
                            }
                        }

Moreover, the constructor might contain parameters while there is none which refers to an object to initiate. So, making sure the ->newInstanceArgs() call happens only if parameters count is > 0

                    }
                }
 
                if(isset($parameters) && count($parameters) > 0){
                    $this->instances[$reflectionClass->name] = $reflectionClass->newInstanceArgs($parameters);
                } else {
                    $this->instances[$reflectionClass->name] = $reflectionClass->newInstance();
                }
            }
        }
 
        // @todo probably throw an exception if null would be returned
        return isset($this->instances[$reflectionClass->name]) ? $this->instances[$reflectionClass->name] : null;
    }
}

The result is still the same

object(Router)#10 (2) {
  ["request":"Router":private]=>
  object(Request)#7 (0) {
  }
  ["response":"Router":private]=>
  object(Response)#11 (1) {
    ["request":"Response":private]=>
    object(Request)#7 (0) {
    }
  }
}

You should be aware of that the above code will not work for interfaces or extended objects. Due to their nature it won’t be possible to auto-inject them. Imagine the following:

interface Router
{
}
abstract class CommonRouter
{
}
class ConcreteRouter extends CommonRouter implements Router
{
}
class AnotherConcreteRouter extends CommonRouter implements Router
{
}
 
class FirstWontWorkExample
{
    public function __construct(Router $router)
    {
    }
}
 
class SecondWontWorkExample
{
    public function __construct(CommonRouter $router)
    {
    }
}

The injector cannot know whether ConcreteRouter or AnotherConcreteRouter should be injected if you do use Router or CommonRouter as type. This is absolutely fine for manual injection, but won’t work for autowiring (adding functionality to the DI-container to add mappings or such like to solve this, still wouldn’t be autowiring obviously, because you need to manually add/define such a mapping and without autowiring this sort of container becomes useless. A hybrid service-locator/di-container or a service locator would be the better choice in such cases imo). As far as I know, in Java the DI Container would throw an exception in such a case.

How about the problem I mentioned: Shared vs non-shared? Let us assume we do have a Router and we do have several Route objects. Now it would make sense if these Route-objects aren’t shared because every route object has different data. However, these route objects are only ever used within the Router. There’s no sense having them in the DI-container imo. The only reason I can think of having a non-shared object in the DI-container would be a Database object in case you are writing something to compare two databases. For that you might want to have the same database object with different login information/server details, twice. Then again I am wondering if dependency injection is the right tool and would argue rather use a service locator for this specific task.

Well, probably just a matter of taste. Let’s continue the Injector by adding setter injection. It is pretty much like constructor injection

$reflectionMethods = $reflectionClass->getMethods();
foreach ($reflectionMethods as $reflectionMethod) {
    $parameters = array();
    if ($reflectionMethod->isPublic() 
       && !$reflectionMethod->isConstructor() 
       && !$reflectionMethod->isDestructor()) {
        $reflectionMethodParameters = $reflectionMethod->getParameters();
        foreach ($reflectionMethodParameters as $reflectionParameter) {
            $pattern = '/^Parameter #\d+ \[ <\w+>'
                     . ' (?P<class>[a-zA-Z_\x7f-\xff][a-zA-Z0-9\\\\_\x7f-\xff]*)'
                     . ' \$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* \]$/is';
            preg_match($pattern, $reflectionParameter, $matches);
            if (isset($matches['class'])) {
                if (isset($this->instances[$matches['class']])) {
                    $parameters[] = $this->instances[$matches['class']];
                } else {
                    $parameters[] = $this->register($matches['class']);
                }
            }
        }
        if (isset($parameters) && count($parameters) > 0) {
            $reflectionMethod->invokeArgs($this->instances[$reflectionClass->name], $parameters);
        }
    }
}

However, we might run into trouble here, because it is very likely that you might have methods which should not be injected automatically. Hence I’d just modify the above to only consider methods which starts with „inject“ as in „injectRequest“. So I am modifying if ($reflectionMethod->isPublic() && !$reflectionMethod->isConstructor() && !$reflectionMethod->isDestructor()) { to if ($reflectionMethod->isPublic() && !$reflectionMethod->isConstructor() && !$reflectionMethod->isDestructor() && substr($reflectionMethod->name, 0, 6) == "inject") {

Additionally, we can implement interface based injection. Assuming that you define an interface which name ends with Dependency or begins with dependsOn we can check the method(s) defined in that interface and call that method in the given class to inject the dependency. That would look like this:

$reflectionInterfaces = $reflectionClass->getInterfaces();
foreach ($reflectionInterfaces as $interface) {
    if (substr($interface->name, -10) == "Dependency" 
       || substr($interface->name, strrpos($interface->name, '\\') + 1, 9) == 'dependsOn') {
        $reflectionInterfaceMethods = $interface->getMethods();
        foreach ($reflectionInterfaceMethods as $reflectionInterfaceMethod) {
            $parameters = array();
            if ($reflectionInterfaceMethod->isAbstract()) {
                $reflectionInterfaceMethodParameters = $reflectionInterfaceMethod->getParameters();
                foreach ($reflectionInterfaceMethodParameters as $reflectionParameter) {
                    $pattern = '/^Parameter #\d+ \[ <\w+>'
                             . ' (?P<class>[a-zA-Z_\x7f-\xff][a-zA-Z0-9\\\\_\x7f-\xff]*)'
                             . ' \$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* \]$/is';
                    preg_match($pattern, $reflectionParameter, $matches);
                    if (isset($matches['class'])) {
                        if (isset($this->instances[$matches['class']])) {
                            $parameters[] = $this->instances[$matches['class']];
                        } else {
                            $parameters[] = $this->register($matches['class']);
                        }
                    }
                }
 
                if (isset($parameters) && count($parameters) > 0) {
                    if ($reflectionClass->hasMethod($reflectionInterfaceMethod->name)) {
                        $reflectionMethod = $reflectionClass->getMethod($reflectionInterfaceMethod->name);
                        $reflectionMethod->invokeArgs($this->instances[$reflectionClass->name], $parameters);
                    }
                }
            }
        }
    }
}

This does work pretty well:

interface DatabaseDependency
{
    public function setDatabase(Database $router);
}
 
class LyraRouter implements DatabaseDependency
{
    public function setDatabase(Database $database)
    {
       $this->db = $database;
    }
}

works as well as using dependsOnDatabase. Which means you could use all three parts: constructor-, setter- and interface injection. Setter injection requires your method name to start with „inject“ and Interface injection just requires an interface which starts with „dependsOn“ or ends with „Dependency“. Time to optimize the code a bit because there is some duplicated code. Talking about ~115 lines of code right now. I came up with:

/**
 * Class LyraInjector
 * @package Lyra\helpers
 */
class LyraInjector
{
    private $reflected = array();
    private $instances = array();
 
    /**
     * @param $class
     * @return mixed
     */
    public function register($class)
    {
        if (!isset($this->reflected[$class])) {
            $this->reflected[$class] = new \ReflectionClass($class);
        }
 
        $reflectionClass = $this->reflected[$class];
        if ($reflectionClass instanceof \ReflectionClass && $reflectionClass->isInstantiable()) {
            if (!isset($this->instances[$reflectionClass->name])) {
                $this->constructorInjection($reflectionClass);
                $this->setterInjection($reflectionClass);
                $this->interfaceInjection($reflectionClass);
            }
        }
 
        return $this->instances[$reflectionClass->name];
    }

Could add a few if’s to make it possible to disable the specific injection variant for performance reasons. If you’re never going to use the interface injection part there is no sense in iterating over interfaces at all. If you’re using interface injection you might not require setter injection and would save a bit because there is no need to iterate over all methods of an object.

That’s the part which handles the constructor injection

    /**
     * @param \ReflectionClass $reflectionClass
     * @return void
     */
    private function constructorInjection(\ReflectionClass $reflectionClass)
    {
        $parameters = array();
        $reflectionConstructor = $reflectionClass->getConstructor();
        if (!is_null($reflectionConstructor)) {
            $parameters = $this->parseReflectionParameters($reflectionConstructor->getParameters());
        }
 
        if (isset($parameters) && count($parameters) > 0) {
            $this->instances[$reflectionClass->name] = $reflectionClass->newInstanceArgs($parameters);
        } else {
            $this->instances[$reflectionClass->name] = $reflectionClass->newInstance();
        }
    }

As I do use the same mechanic to parse parameters for all variants (constructor, setter, interface) it did make sense to de-duplicate using the following function

    /**
     * @param $reflectionParameters
     * @return array
     */
    private function parseReflectionParameters($reflectionParameters)
    {
        $pattern = '/^Parameter #\d+ \[ <\w+>'
            . ' (?P<class>[a-zA-Z_\x7f-\xff][a-zA-Z0-9\\\\_\x7f-\xff]*)'
            . ' \$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* \]$/is';
        $parameters = array();
        foreach ($reflectionParameters as $reflectionParameter) {
            if ($reflectionParameter instanceof \ReflectionParameter) {
                preg_match($pattern, $reflectionParameter, $matches);
                if (isset($matches['class'])) {
                    if (isset($this->instances[$matches['class']])) {
                        $parameters[] = $this->instances[$matches['class']];
                    } else {
                        $parameters[] = $this->register($matches['class']);
                    }
                }
            }
        }
 
        return $parameters;
    }

This one for setter injection. It might make sense to add another method which would make it possible to modify „inject“ so that it could also start with „foobar“ 🙂

    /**
     * @param \ReflectionClass $reflectionClass
     */
    private function setterInjection(\ReflectionClass $reflectionClass)
    {
        $reflectionMethods = $reflectionClass->getMethods();
        foreach ($reflectionMethods as $reflectionMethod) {
            if ($reflectionMethod->isPublic() && !$reflectionMethod->isConstructor() 
            && !$reflectionMethod->isDestructor() && substr($reflectionMethod->name, 0, 6) == "inject") {
                $parameters = $this->parseReflectionParameters($reflectionMethod->getParameters());
                if (isset($parameters) && count($parameters) > 0) {
                    $reflectionMethod->invokeArgs($this->instances[$reflectionClass->name], $parameters);
                }
            }
        }
    }

and finally the interface injection part. Similar to setter injection, it might make sense to add methods which would make Dependency und dependsOn replaceable. Just noticed one problem here, though. substr($interface->name, strrpos($interface->name, '\\') + 1, 9) == 'dependsOn' will work as long as namespaces are in use. Just seen that reflection offers „getShortName“ which returns the name without the namespace. Hence modifying that to substr($interface->getShortName(), 0, 9).

    /**
     * @param \ReflectionClass $reflectionClass
     */
    private function interfaceInjection(\ReflectionClass $reflectionClass)
    {
        $reflectionInterfaces = $reflectionClass->getInterfaces();
        foreach ($reflectionInterfaces as $interface) {
            if (substr($interface->name, -10) == "Dependency" || substr($interface->getShortName(), 0, 9) == 'dependsOn') {
                $reflectionInterfaceMethods = $interface->getMethods();
                foreach ($reflectionInterfaceMethods as $reflectionInterfaceMethod) {
                    if ($reflectionInterfaceMethod->isAbstract()) {
                        $parameters = $this->parseReflectionParameters($reflectionInterfaceMethod->getParameters());
                        if (isset($parameters) && count($parameters) > 0) {
                            if ($reflectionClass->hasMethod($reflectionInterfaceMethod->name)) {
                                $reflectionMethod = $reflectionClass->getMethod($reflectionInterfaceMethod->name);
                                $reflectionMethod->invokeArgs($this->instances[$reflectionClass->name], $parameters);
                            }
                        }
                    }
                }
            }
        }
    }
}

This does pretty much what I want it to do, let’s play around with it

usage

// you could also name this RequestDependency
interface dependsOnRequest{
    // it does not matter if you name the method attach, set, inject, ..
    public function attachRequest(Request $request);
}
interface ResponseDependency
{
    public function insertResponse(Response $response);
}
class Dummy
{
    // constructor injection example
    public function __construct(HelloWorld $hello)
    {
        $this->foo = $hello;
    }
}
class Sanitizer
{
}
class Request
{
    private $sanitizer;
    // just an example for setter injection
    public function injectSanitizer(Sanitizer $sanitizer)
    {
        $this->sanitizer = $sanitizer;
    }
}
class Response implements dependsOnRequest
{
    private $request;
    // implemented due to the interface
    public function attachRequest(Request $request)
    {
        $this->request = $request;
    }
}
class Router implements dependsOnRequest, ResponseDependency
{
    // constructor injection example
    public function __construct(Dummy $dummy)
    {
        $this->dummy = $dummy;
    }
    // implemented due to dependsOnRequest
    public function attachRequest(Request $request)
    {
        $this->request = $request;
    }
    // implemented due to ResponseDependency
    public function insertResponse(Response $response)
    {
        $this->response = $response;
    }
}

Now thats how I do use the injector

$inj = new LyraInjector();
$router = $inj->register('Router');
 
var_dump($router);

and the result is

object(Router)#4 (3) {
  ["dummy"]=>
  object(Dummy)#7 (1) {
    ["foo"]=>
    object(HelloWorld)#9 (0) {
    }
  }
  ["request"]=>
  object(Request)#13 (1) {
    ["sanitizer":"Request":private]=>
    object(Sanitizer)#17 (0) {
    }
  }
  ["response"]=>
  object(Response)#18 (1) {
    ["request":"Response":private]=>
    object(Request)#13 (1) {
      ["sanitizer":"Request":private]=>
      object(Sanitizer)#17 (0) {
      }
    }
  }
}

awesome or awful?

How does it deal with circular dependencies?

class Water
{
    public function injectCO2(CO2 $co2)
    {
        $this->co2 = $co2;
    }
}
class CO2
{
    public function injectWater(Water $water)
    {
        $this->water = $water;
    }
}
class Bottle
{
    public function injectWater(Water $water)
    {
        $this->water = $water;
    }
    public function injectCO2(CO2 $co2)
    {
        $this->co2 = $co2;
    }
}
$inj = new LyraInjector();
$bottle = $inj->register('Bottle');
 
var_dump($bottle);

results in

object(Bottle)#15 (2) {
  ["water"]=>
  object(Water)#2 (1) {
    ["co2"]=>
    object(CO2)#11 (1) {
      ["water"]=>
      *RECURSION*
    }
  }
  ["co2"]=>
  object(CO2)#11 (1) {
    ["water"]=>
    object(Water)#2 (1) {
      ["co2"]=>
      *RECURSION*
    }
  }
}

works, I’d say. To be continued in Part 3 of this .. series.

1 Comment

  • eugen

    19. November 2016 at 19:23 Antworten

    nice article, thanks for work, you save a lot of my time.

Post a Comment