Introducing Dice, a minimal Dependency Injection Container for PHP

I’m not usually one to release code into the wild but here’s something which is both mature and unique enough to be worthwhile.

Dice is a Convention-Over-Configuration Dependency Injection Container I’ve been working on. At its most basic level and with zero configuration it lets you build an object graph with little effort:


class A { 
    private $b; 

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

class B { 
    private $c,$d; 

    public function __construct(C $c, D $d) { 
        $this->c = $c; 
        $this->d = $d; 
    } 
} 

class C { 

} 

class D { 
    private $e; 
     
    public function __construct(E $e) { 
        $this->e = $e; 
    } 
} 

class E { 
     
} 


$dice = new Injection; 
$a = $dice->create('a'); 
print_r($a);

Which will output:


A Object 
( 
    [b:A:private] => B Object 
        ( 
            [c:B:private] => C Object 
                ( 
                ) 

            [d:B:private] => D Object 
                ( 
                    [e:D:private] => E Object 
                        ( 
                        ) 

                ) 

        ) 

)

As a practical example with a minimal configuration it allows you to do this:


//Firstly create a rule 
$rule = new DiceRule; 
$rule->shared = true; 

//PDO will be constructed by the container with these parameters: 
$rule->constructParams = array('mysql:host=127.0.0.1;dbname=mydb', 'username', 'password'); 

$dice->addRule('PDO', $rule); 

$pdo = $dice->create('PDO'); 
$pdo2 = $dice->create('PDO'); 
var_dump($pdo === $pdo2); //TRUE 


//And any class which asks for an instance of PDO will be given the same instance: 
class MyClass { 
    public $pdo; 
    public function __construct(PDO $pdo) { 
        $this->pdo = $pdo; 
    } 
} 

$myobj = $dice->create('MyClass'); 
var_dump($pdo === $myobj->pdo);

You would be able to define any class in the system with a dependency on PDO and it would be passed the shared instance of the PDO object on creation. This goes for a class which is any depth down the object graph:


class MyApplication {

	public function __construct(MyController $controller) {
		
	}

}

class MyController {
	public function __construct(PDO $pdo) {

	}
}

$dice->create('MyApplication');


In this scenario, “MyController” would still be passed the shared instance of PDO when it was created.

One important distinction that Dice makes is many other Dependency Injection Containers is that it doesn’t need any metadata about most of the classes it’s creating. Only those which have special rules such as being shared across the application

Full Documentation/Downlad

Any comments/suggestions?

Yes, the temptation to call it TinyDIC or something was large but I thought I’d keep it serious because its uses are far fetched.

I had to read it twice (second time in the afternoon when I could comprehend what I’m reading). This seems very useful. For me, I’ve been trying to push the limits of php’s ability to gain performance on multiple processes through forking, and most of the time that means duplicating out a new object to force a new connection.

Awesome! lol

May be worth comparing with Pimple – an even tinier DIC.

As far as I’m aware Pimple doesn’t support creation of object graphs or automatically resolve dependencies so is more of a way of creating a centralised set of factories than a true DI Container.

For instance, in Pimple, this breaks it:


class Foo {
		
}


$pimple = new Pimple;
$foo = $pimple['Foo'];

var_dump($foo);

As such, Pimple requires a lot of interaction with the application developer and can’t just sit transparently at the top level of the application.

See the wikipedia entry here: http://en.wikipedia.org/wiki/Dependency_injection#Automatically_injected_dependency for a demonstration of what I’m trying to achieve from a DIC

A centralized set of factories actually seems to be a good description for exactly what a DIC is. Since a factory is a “method for creating objects” and a DIC is “an object that knows how to instantiate and configure objects.”

Your DIC, and automated injection in general, seems to try to reduce the number of factories we must define. It’s an interesting feature and worth exploring. Though, if you’re going to implement that feature, then calling your library minimalist may not be a fitting description.

I’m not sure that fully qualifies. If it cant create arbitrary objects and resolve their dependencies its usefulness is limited in scope because it has to be called every time a shared dependency is needed. The definition here: http://www.loosecouplings.com/2011/01/dependency-injection-using-di-container.html suggests that "The entry points are the only places that should be aware that you are using a container. Everything else just gets supplied the required dependencies and doesn’t know or care where they came from. " with Pimple this isn’t the case. If I have an object 10 steps down a chain that has a dependency which isn’t shared by its parents, pimple can’t create this easily unless the entire object graph is defined in a single factory.

I suppose that depends on your definition of minimal. On one hand, “minimal” would be a simple factory, on the other, that requires the application developer to write a significant more code to use it. Although dice itself is small, my goal was the minimise the amount of code the end-user has to write in order to use it, so in this sense it is minimalist.

Seems to me that it is the case. For example:

$container = new Pimple();

// Tell Pimple how to create E objects
$container['E'] = function ($container) {
    return new E();
};

// Tell Pimple how to create D objects
$container['D'] = function ($container) {
    // This is the entry point to D,
    // and entry points like this are the only places aware of the container.
    // Classes D and E are still unaware of the container.
    // They just get their dependencies without knowing where they came from.
    return new D( $container['E'] );
};

I suppose that depends on your definition of minimal. On one hand, “minimal” would be a simple factory, on the other, that requires the application developer to write a significant more code to use it. Although dice itself is small, my goal was the minimise the amount of code the end-user has to write in order to use it, so in this sense it is minimalist.

I guess you’re right that it depends on our definition of minimal. In my experience, minimalist usually refers to the amount of code and features in a library. For example, a framework can minimize the amount of code the end-user has to write, but no one would claim that a framework is minimalist.

Of course, but the container needs to know about every class it’s ever going to create. In my opinion, that defeats the purpose and requires far too much configuration for any non-trivial real world project. Every time a class is added to the system the container needs to be reconfigured. Not good!

True, which is why I only implemented features which are needed and avoided where possible the ability to achieve the same thing multiple ways. In my opinion, Dice doesn’t contain anything that a DIC shouldn’t and contains the fewest number of features that make it viable for real-world projects. I don’t believe pimple does enough to make its use worthwhile; it requires just as much (if not more) work than just manually injecting every dependency, so I suppose that’s more “minimal” but if it doesn’t fulfil the requirements or add any benefit, what’s the point? I could write a factory that referenced other factories and it would essentially be identical to pimple, yet “more minimal”. Dice isn’t actually much bigger than pimple (and almost half the code is for the optional XML loader).

Those kind of DICs are already being used in non-trivial, real-world projects, where use of the DIC is worthwhile and certainly better than manually injecting every dependency throughout your code. But that’s beside the point. I didn’t come here to debate manual vs automated injection, and I’m not even sure what my opinion is on that yet. I came here because you emphasized “tiny” and “minimal,” so it seemed appropriate to mention Pimple. Except, it turns out, in your view, any container that doesn’t use automated injection is not a “true” container.

I’m not quite sure why the debate over “minimal” is even happening, I clearly stated my project goals at the top of the page I linked to. It uses the smallest amount of code needed to complete the project goals with the fewest number of available options while retaining enough functionality to be useful in applications which need to push the limits of what the container can do.

As for not a “true” container, my opinion is that if you’re going make a decision to use a container but have to manually supply (often repeating) code for 99% of what the container is doing then the argument for adding an extra layer of complexity to the application is almost non-existent.

After re-reading that, I’ll add some context:

A container can save work and repeated code by using rules rather than hardcoded factories. For instance being able to say “Any class which asks for PDO in its constructor should be given this instance” or “Any class which inherits from Controller and asks for an instance of Session should be given this instance” instead of having to write factories for every single class in the system makes the container far easier to use, and means the developer doesn’t need to repeat him/herself for each Controller that asks for a session instance. The most immediately obvious benefit is that a constructor can be changed completely and it’ll just follow all the rules defined in the DIC- the DIC will not need to be reconfigured every time the class API changes. Whether or not a class has a dependency is irrelevant to the DIC, it just has to follow the rules on what it’s doing (creating a new instance, using an existing instance) when it comes across a class which does ask for dependencies.