Circular dependency - Injecting objects that are directly depended on each other

Ilia Rostovtsev picture Ilia Rostovtsev · Jun 6, 2014 · Viewed 10k times · Source

I have used Dice PHP DI container for quite a while and it seems the best in terms of simplicity of injecting dependencies.

From Dice Documentation:

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

class B {
    
}

$dice = new \Dice\Dice;    
$a = $dice->create('A');
var_dump($a->b); //B object

However, when you have to use objects that are directly dependent on each other, the finall result is server error, because of the infinite loop.

Example:

class A {
    public $b;

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

class B {
    public $a;

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

Author of Dice says that there is no way to construct an object from the A or B classes. As:

  • An 'A' object requires a 'B' object to exist before it can be created
  • But a 'B' object requires an 'A' object to exist before it can be created

Author says, that this limitation concerns all DI containers!


Question:

What would be the best solution for overcoming this problem nicely without changing initial code? Could anyone provide an example of using other DI containers, when it would be possible to run exampled code without bulky workarounds?

Answer

Tom B picture Tom B · Jun 6, 2014

As mentioned on your post on the Dice github ( https://github.com/TomBZombie/Dice/issues/7 ), the only way to resolve without removing the circular dependency is to refactor one of the classes to use setter injection:

class A {
    public $b;

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


class B {
    public $a;

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

This allows the objects to be constructed:

$b = new B();
$a = new A($b);
$b->setA($a);

With the original code:

class A {
    public $b;

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

class B {
    public $a;

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

You cannot construct it and run into the same problem as the container:

$b = new B(new A(new B(new A(new B(.............))))

The problem with having a container work around this issue using a hack such as ReflectionClass::newInstanceWithoutConstructor is that your objects are now dependent on creation logic which uses this method. You essentially couple the code to the container which is a poor design as your code is now no longer portable and cannot be used without the container to perform the object construction.