A dependency injection container is a design pattern for the delivery of an objects with all dependencies injected. In a recent blog Tom Butler compared several such containers for speed and memory consumption. Main outcome: the Dice library behaved pretty good and the Zend/DI library behaved pretty bad. What these libraries have in common is that they use autowiring i.d. reflection to find out what class requires what injection.

Such a library with implementation may look like this:

class A
{

    protected $counter = 21;

    public function getCount()
    {
        return $this->counter;
    }

}

class B
{

    protected $counter = 12;
    protected $a;

    public function __construct(A $a)
    {
        $this->a = $a;
    }

    public function getCount()
    {
        return $this->counter + $this->a->getCount();
    }

}

class C
{

    protected $b;
    protected $counter = 10;

    public function __construct(B $b)
    {
        $this->b = $b;
    }

    public function add()
    {
        return $this->counter + $this->b->getCount();
    }

}

class DI
{

    public function get($class)
    {
        $reflection = new ReflectionClass($class);
        $constructor = $reflection->getConstructor();
        $parameters = array();
        if (is_object($constructor)) {
            $parameters = $constructor->getParameters();
        }
        $args = array();

        if (count($parameters) > 0) {
            foreach ($parameters AS $parameter) {
                if (is_object($parameter)) {
                    $class = $parameter->getClass();
                    if (is_object($class)) {
                        $className = $class->name;
                        $args[] = $this->get($className);
                    }
                }
            }
        }

        return $reflection->newInstanceArgs($args);
    }

}

$di = new DI;
// equivalent to
// $b = new C(new B(new A()));
$b = $di->get('C');
echo $b->add();  // prints 43.


The DI container figures out by reflection that it should return a new C(new B(new A()))) instance.

In the Butler article Zend/DI is slower than Dice by a factor of 20 to 40. Several commentators mention caching but caching is never an elegant solution. Cloning the container is not mentioned. In the selected benchmark cloning makes the slow code go away immediately. It is also worthwhile to look at the code base of both libraries. Xdebug reveals that with Zend/DI a single request triggers multiple calls to a method called processClass(). This seems excessive and a simple measure (check if class is already processed) reduces the load by 30%.

if (!array_key_exists($class, $this->classes)) {
            $this->processClass($class);
        }


Several other methods get multiple calls so there is room for improvement so it seems.

So as solutions we have caching, cloning and refactoring. The best solution remains not using a DI container of this type at all. Code should be explicit and statements should not appear by magic. In addition commenter Matthew Weier O’Phinney explains Zend discourages Zend/Di due to performance issues and advocates Zend\ServiceManager instead.

Advertisements