Tom Butler's programming blog

Dice - PHP Dependency Injection Container

Introduction

Last updated 15/11/2018

Dice is a minimalist Inversion of Control (IoC) container (Often called a Dependency Injection Container) for PHP.

Dice allows developers to move object creation logic out of their application logic. This has many advantages when it comes to OOP theory, but also makes the developers life easier. It tries to be as minimal and unobtrusive as possible. Everything is done to minimise and simplify application code, making the application developers job easier and making the learning curve as shallow as possible.

Download/View on GitHub

Project goals

Dice has been designed with the following goals:

  • Minimalist, lightweight. Currently Dice is a single 100 line class.
  • Every aspect should be configurable but...
  • ...use a very strict Convention-Over-Configuration approach. The container should only require configuration where absolutely necessary
  • Basic functionality should work with zero configuration
  • The application developer should not have to interact with the container often during development
  • The container should not need to be reconfigured every time a dependency is added to a class or a class which has dependencies is added to the application
  • Following the guidelines above should enable the container to be easy to learn to use
  • Be configurable solely with PHP code. Other syntaxes such as XML should be entirely optional.

Download/View on GitHub

Introduction

Dice is a minimalist Inversion of Control (IoC) container (Often called a Dependency Injection Container) for PHP.

Dice allows developers to move object creation logic out of their application logic. This has many advantages:

1) It makes your life as a developer easier!

Imagine this:

$a = new A(new B, new C, new D(new E, new F));

With Dice and zero configuration, this can be expressed as:

$dice = new \Dice\Dice;
$a $dice->create('A');

All the dependencies (And dependencies of those dependencies) are automatically resolved.

And if you add a class to the system? Imagine you want to add a class to an existing project which has a dependency on your existing PDO shared dependency. With Dice, you simply define the class with the dependency:

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

    }
}

If PDO is already in use elsewhere in the project, this is all you need to do to have PDO passed to MyClass' constructor! You don't need to configure Dice at all or tell it anything about what dependencies MyClass has!

2) Improved maintainability.

Take the example above. If the definition of the class C is modified during the development lifecycles and now has a dependency on a class called X, instead of finding everywhere that C is created and passing an instance of X to it, this is handled automatically by the IoC Container. You can add dependencies to any class any any time during development and only have to alter the class definition without needing to scout your codebase for "new" keywords to add the dependencies or even reconfigure Dice!

3) Avoid "couriers"

Only objects which use the dependency will have it. It's very easy to accidentally use the courier anti-pattern when using Dependency Injection.

4) Higher flexibility

By using Dependency Injection, your application isn't hardcoded to a particular instance.

Consider this:

class {
    private 
$b;
    private 
$c;
    private 
$d;

    public function 
__construct()
        
$this->= new B;
        
$this->= new C;
        
$this->= new D(new E, new F);
    }

}

With this code, it's impossible to use a subclass of B in place of the instance of B. A is very tightly coupled to its dependencies. With dependency injection, any of the components can be substituted:

class {
    private 
$b;
    private 
$c;
    private 
$d;

    public function 
__construct(B $bC $cD $d)
        
$this->$b;
        
$this->$c;
        
$this->$d;
    }

}

Here, B could be any subclass of B configured in any way possible. To get technical, this allows for a far greater Separation of Concerns because A never has to worry about configuring its dependencies, they're given to it in a state that is ready to use. By giving less responsibility to the class, flexibility is greatly enhanced because A can be reused with any variation of B, C and D instances.

For more information and specific details on why this is problematic, see Misko Hevery's article Flaw: Constructor does Real Work

5) You don't have to worry about locating dependencies or changing constructor arguments ever again.

Forget service locators, registries and 99% of factories. By using Dice you can change the constructor parameters adding/removing dependencies on a whim without worrying about reconfiguring Dice or side-effects throughout your code. This enables far better encapsulation, objects will never know or need to know what dependencies other objects have! Because object creation is all abstracted to the IoC container, and Dice doesn't need reconfiguring each time you alter a constructor or add a class, making changes is incredibly easy!

Once Dice is up and running and handling your application's dependencies, you can simply add a class to the system and it will just work without even telling Dice anything about it. Just add:

class {
    public function 
__construct(PDO $pdoC $c) {

    }
}

And require it in one of your existing classes:

class ExistingA {
    public function 
__construct(B $b) {
    }
}

And it will just work without any additional configuration! You don't need to worry that you've changed an existing class' constructor as it will automatically be resolved and you don't need to worry about locating or configuring the dependencies that the new class needs!

Sounds too good to be true? See the examples below.

Or download/view on GitHub

Using Dice Dependency Injection Container

  1. Basic usage
    1. Object graph creation
    2. Providing additional arguments to constructors
  2. Shared dependencies
    1. Using rules to configure shared dependencies
  3. Configuring the container with rules
    1. Substitutions
    2. Inheritance
    3. Constructor Parameters
    4. Setter injection
    5. Default rules
    6. Named instances
    7. Sharing instances for a single tree
  4. Additional features
    1. Callbacks
    2. Rule cascading
    3. Namespace support
    4. Referencing other instances in rules
    5. Using constants in rules
    6. Using global variables in rules
    7. Method chaining and factories
  5. Defining rules using JSON
    1. Basic usage

1. Basic usage

Why is Dice different? A lot of DICs require that you provide some configuration for each possible component in order just to work.

Dice takes a convention-over-configuration approach and uses type hinting to infer what dependencies an object has. As such, no configuration is required for basic object graphs.

1.1 Object graph creation

class {
    private 
$b;

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

class 
{
    private 
$c,$d;

    public function 
__construct(C $cD $d) {
        
$this->$c;
        
$this->$d;
    }
}

class 
{

}

class 
{
    private 
$e;

    public function 
__construct(E $e) {
        
$this->$e;
    }
}

class 
{

}


$dice = new \Dice\Dice;
$a $dice->create('A');
print_r($a);
Which creates: A Object
(
    [
b:A:private] => B Object
        
(
            [
c:B:private] => C Object
                
(
                )

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

                )

        )

)

At its simplest level, this has removed a lot of the initialisation code that would otherwise be needed to create the object graph.

1.2 Providing additional arguments to constructors

It's common for constructors to require both dependencies which are common to every instance as well as some configuration that is specific to that particular instance. For example:

class {
    public 
$name;
    public 
$b;

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

Here, the class needs an instance of B as well as a unique name. Dice allows this:

$a1 $dice->create('A', ['FirstA']);
$a2 $dice->create('A', ['SecondA']);

echo 
$a1->name// "FirstA"
echo $a2->name// "SecondA"

The dependency of B is automatically resolved and the string in the second parameter is passed as the second argument. You can pass any number of additional constructor arguments using the second argument as an array to $dice->create();

Please note: It is preferable to do this using a rule and constructParams. See the section on rules for more information.

2. Shared dependencies

By far the most common real-world usage of Dependency Injection is to enable a single instance of an object to be accessible to different parts of the application. For example, Database objects and locale configuration are common candidates for this purpose.

Dice makes it possible to create an object that is shared throughout the application. Anything which would traditionally be a global variable, a singleton, accessible statically or accessed through a Service Locator / Repository is considered a shared object.

Any class constructor which asks for an instance of a class that has been marked as shared will be passed the shared instance of the object rather than a new instance.

2.1 Using rules to configure shared dependencies

The method of defining shared objects is by Rules. See the section on Rules below for more information. They are used to configure the container. Here's how a shared object is defined using a rule.

Dice accepts a rule for a given class an applies it each time it creates an instance of that class. A rule is an array with a set of options that will be applied when an instance is requested from the container.

This example uses PDO as this is a very common use-case.

//create a rule to mark PDO instances as shared
$rules = [
    
'PDO' => ['shared' => true]
];

//Apply the rule to the container
$dice $dice->addRules($rules);

//Now any time PDO is requested from Dice, the same instance will be returned
$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); //TRUE

Here, both instances of PDO would be the same. However, because this is likely to be the most commonly referenced piece of code on this page, to make this example complete, the PDO constructor would need to be configured as well:

$rules = [
    
//Apply these rules to instances of PDO
    
'PDO' => [
         
//Mark the class as shared so the same instance is returned each time
         
'shared' => true,
         
//The constructor arguments that will be supplied when the instance is created
         
'constructParams' => [
             
'mysql:host=127.0.0.1;dbname=mydb',
             
'username',
             
'password'
          
]
        ]
];

//Apply the rule to the PDO class
$dice $dice->addRules($rules);

//Now any time PDO is requested from Dice, the same instance will be returned
//And will have been constructed with the arugments supplied in 'constructParams'
$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;
    }
}

class 
MyOtherClass {
    public 
$pdo;
    public function 
__construct(PDO $pdo) {
        
$this->pdo $pdo;
    }
}


//Note, Dice is never told about the 'MyClass' or 'MyOtherClass' classes, it can
//just automatically create them and inject the required PDO isntance

$myobj $dice->create('MyClass');
$myotherobj $dice->create('MyOtherClass');

//When constructed, both objects will have been passed the same instance of PDO
var_dump($myotherobj->pdo === $myobj->pdo); //TRUE

The constructParams rule has been added to ensure that every time an instance of PDO is created, it's given a set of constructor arguments. See the section on constructParams for more information.

3. Configuring the container with Dice Rules

In order to allow complete flexibility, the container can be fully configured using rules provided by associative arrays rules are passed to the container using the addRule method:

$dice = new \Dice\Dice;
$rules = [
    
'RuleName' => ['name' => 'value']
];
$dice $dice->addRules($rules);

By default, rule names match class names so, to apply a rule to a class called A you would use:

$dice = new \Dice\Dice;

$rules = [
    
'A' => ['name' => 'value']
];
$dice $dice->addRules($rules);

$a $dice->create('A');

Each time an instance of A is created by the container it will use the rule defined by $rule

You can apply different rules to different classes by specifying additional entries in the $rules array:

$rules = [
    
'A' => ['name' => 'value'],
    
'B' => ['name' => 'value']
];

$dice $dice->addRules($rules);

You can also call addRules as many times as you like to assign additional rules to the container.

Additionally, you can reduce the amount of code required by passing the array directly to the addRules method:

$dice $dice->addRules([
    
'A' => ['name' => 'value'],
    
'B' => ['name' => 'value']
]);

Available Properties

Dice Rules can be configured with these properties:

  • shared (boolean) - Whether a single instance is used throughout the contiainer. View Example
  • inherit (boolean) - Whether the rule will also apply to subclasses (defaults to true). View Example
  • constructParams (array) - Additional parameters passed to the constructor. View Example
  • substitutions (array) - key->value substitutions for dependencies. View Example
  • call (multidimensional array) - A list of methods and their arguments which will be called after the object has been constructed. View Example
  • instanceOf (string) - The name of the class to initiate. Used for named instances. View Example
  • shareInstances (array) - A list of class names that will be shared throughout a single object tree. View Example

3.1 Substitutions

When constructor arguments are type hinted using interfaces or to enable polymorpsim, the container needs to know exactly what it's going to pass. Consider the following class:

class {
    public function 
__construct(Iterator $iterator) {

    }
}

Clearly, an instance of "Iterator" cannot be used because it's an interface. If you wanted to pass an instance of B:

class implements Iterator {
    
//...
}

The rule can be defined like this:

//When a constructor asks for an instance of Iterator pass it an instance of B instead
$rule =

$dice $dice->addRules([
    
'A' => [
        [
'substitutions' => [
             
'Iterator' => 'B'
            
]
        ];
    ]);

$a $dice->create('A');

The reason that ['substitutions' => ['iterator' => $dice->create('B')]] is not used is that this creates a B object there and then. Using the instance name as a string means that an instance of B is only created at the time it's required.

However, what if If the application required this?

$a = new A(new DirectoryIterator('/tmp'));

There are three ways this can be achieved using Dice.

1. Direct substitution, pass the fully constructed object to the rule:

$dice $dice->addRules(['A' => [
    
'substitutions' => [
        
'Iterator' => new DirectoryIterator('/tmp')
    ]
]);

$a $dice->create('A');

2. Factory substitution with closures

You can use an array with the key set to the constant \Dice\Dice::INSTANCE with a closure and the instance returned from the closure will be used as the substitution. This is done just-in-time so will be called as the class it's been applied to is instantiated.

$dice $dice->addRules([
    
'A' => [
        
'substitutions' =>
            [
                
'Iterator' => [\Dice\Dice::INSTANCE => function() {
                            return new 
DirectoryIterator('/tmp');
                }]
            ]
        ]
]);
$a $dice->create('A');

The code

'Iterator' => [\Dice\Dice::INSTANCE => function() {
                        return new 
DirectoryIterator('/tmp');
                    }]

Tells dice to call the closure and use the returned instance as the substitution

3. Named instances. See the section on Named instances for a more detailed explanation of how this works.

$dice->addRules([
    
//Define a named instance called $MyDirectoryIterator
    //and configure the instance constructor arguments

    
'$MyDirectoryIterator' => [
        
//An instance of the DirectoryIterator class will be created
        
'instanceOf' => 'DirectoryIterator',
        
//When the DirectoryIterator is created, it will be passed the string '/tmp' as the constructor argument
        
'constructParams' => ['/tmp']
    ],

    
//Define a rule to use the named instance as a substitution
    //when creating A instances

    
'A' => [
        
'substitutions' =>
            [
                
'Iterator' => [\Dice\Dice::INSTANCE => '$MyDirectoryIterator']
            ]
        ]
    ]
);


//Now, when $a is created, it will be passed the Iterator configured as $MyDirectoryIterator

$a $dice->create('A');

3.2 Rule Inheritance

By default, all rules are applied to any child classes whose parent has a rule. For example:

class {
}

class 
extends {
}


//Mark instances of A as shared
$dice $dice->addRules([
    
'A' => ['shared' => true]
]);

//Get the rule currently applied to 'B' objects
$bRule $dice->getRule('B');

//And B instances will also be shared
var_dump($bRule['shared']); //TRUE

//And to test it:
$b1 $dice->create('B');
$b2 $dice->create('B');

var_dump($b1 === $b2); //TRUE (they are the same instance)

The rule's inherit property can be used to disable this behaviour:

class {
}

class 
extends {
}


//This time mark A as shared, but turn off rule inheritance
$aRule = ['shared' => true'inherit' => false];

$dice $dice->addRules([
    
'A' => [
        
'shared' => true,
        
'inherit' => false
    
]
]);
$bRule $dice->getRule('B');

//Now, B won't be marked as shared as the rule applied to A is not inherited
var_dump($bRule['shared']); //FALSE

//And to test it:
$b1 $dice->create('B');
$b2 $dice->create('B');

var_dump($b1 === $b2); //FALSE (they are not the same instance)

3.3 Constructor parameters

When defining a rule, any constructor parameters which are not type hinted must be supplied in order for the class to be initialised successfully. For example:

class {
    public function 
__construct(B $b$foo$bar) {
    }
}

The container's job is to resolve B. However, without configuration it cannot possibly know what $foo and $bar should be.

These are supplied using:

$dice $dice->addRules([
    
'A' => [
        
'constructParams' => ['Foo''Bar']
    ]
]);

$a $dice->create('A');

This is equivalent to:

new A(new B'Foo''Bar');

Constructor parameter order for dependencies does not matter:

class {
    public function 
__construct($foo$barB $b) {
    }
}

$dice $dice->addRules([
    
'A' => [
        
'constructParams' => ['Foo''Bar']
    ]
]);

$a $dice->create('A')

Dice is smart enough to work out the parameter order and will execute as expected and be equal to:

new A('Foo''Bar', new B);

3.4 Setter injection

Objects often need to be configured in ways that their constructor does not account for. For example: PDO::setAttribute() may need to be called to further configure PDO even after it's been constructed.

To account fo this, Dice Rules can supply a list of methods to call on an object after it's been constructed as well as supply the arguments to those methods. This is achieved using $rule->call:

class {
    public function 
__construct(B $b) {


    }

    public function 
method1($foo$bar) {
        echo 
'Method1 called with ' $foo ' and ' $bar "\n";
    }

    public function 
method2() {
        echo 
"Method2 called\n";
    }
}

//When an A instance is created run methods method1, method1 and method2
$dice $dice->addRules([
    
'A' => [
        
'call' => [
            [
'method1', ['Foo1' ,'Bar1']],
            [
'method1', ['Foo2' ,'Bar2']],
            [
'method2', []]
        ]
    ]
]);
$a $dice->create('A');

This will output:

Method1 called with Foo1 and Bar1
Method1 called with Foo2 
and Bar2
Method2 called

The methods defined using call will get called in the order of the supplied array.

Practical example: PDO

Here is a real world example for creating an instance of PDO.

$dice $dice->addRules([
    
'PDO' => [
        
'constructParams' => [
            
'mysql:host=127.0.0.1;dbname=mydb',
            
'username',
            
'password'
        
],
        
'shared' true,
        
'call' => [
            [
'setAttribute', [PDO::ATTR_DEFAULT_FETCH_MODEPDO::FETCH_OBJ]]
        ]
    ]
]);

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

    }
}

//MyObj will be constructed with a fully initialisd PDO object
$myobj $dice->create('MyClass');

3.5 Default rules

Dice also allows for a rule to apply to any object it creates by applying it to '*'. As it's impossible to name a class '*' in php this will not cause any compatibility issues.

The default rule will apply to any object which isn't affected by another rule.

The primary use for this is to allow application-wide rules. This is useful for type-hinted arguments. For example, you may want any class that takes a PDO object as a constructor argument to use a substituted subclass you've created. For example:

class MyPDO extends PDO {
    
//...
}

Dice allows you to pass a "MyPDO" object to any constructor that requires an instance of PDO by adding a default rule:

class Foo {
    public 
$pdo;

    public function 
__construct(PDO $pdo) {
        
$this->pdo $pdo;
    }
}


//Whenever a PDO instance is type hinted, supply the MyPDO instance instead:
$dice $dice->addRules([
    
'*' => [
        
'substitutions' => ['PDO' => 'MyPDO']
    ]
]);

$foo $dice->create('Foo');
echo 
get_class($foo->pdo); // "MyPDO"

The default rule is identical in functionality to all other rules. Objects could be set to shared by default, for instance.

3.6 Named instances

One of Dice's most powerful features is Named instances. Named instances allow different configurations of dependencies to be accessible within the application. This is useful when not all your application logic needs to use the same configuration of a dependency.

For example, if you need to copy data from one database to another you'd need two database objects configured differently. With named instances this is possible:

class DataCopier {
    public function 
__construct(PDO $database1PDO $database2) {
        
//Do something with $database1 and $database2
        //e.g. copying data from one to another
    
}
}


$dice $dice->addRules([
    
//A rule for the default PDO object
    
'PDO' => [
        
'shared' => true,
        
'constructParams' = [
            
'mysql:host=127.0.0.1;dbname=mydb',
            
'username',
            
'password'
        
]
    ],

    
//Named instance called $Database2
    //This does not directly map to a class name
    
'$Database2' => [
        
'shared' => true,
        
'constructParams' = [
            
'mysql:host=externaldatabase.com;dbname=foo',
            
'theusername',
            
'thepassword'
        
],
        
//When an instance of $Database2 is requested from the container
        //create an instance of PDO
        
'instanceOf' => 'PDO'
    
],

    
//Now set DataCopier to use the two different databases:
    
'DataCopier' => [
        
'constructParams' = [
            [\
Dice\Dice::INSTANCE => 'PDO'],
            [\
Dice\Dice::INSTANCE => '$Database2']
        ]
    ]
]);


$dataCopier $dice->create('DataCopier');

$dataCopier will now be created and passed an instance to each of the two databases.

Once a named instance has been defined, it can be referenced using [\Dice\Dice::INSTANCE => '$name'] by other rules using the Dependency Injection Container in either substitutions or constructor parameters.

Named instances do not need to start with a dollar, however it is advisable to prefix them with a character that is not valid in class names to avoid name clashes with real classes.

3.7 Sharing instances for a specific tree

In some cases, you may want to share a a single instance of a class between every object in one tree but if another instance of the top level class is created, have a second instance of the tree.

For instance, imagine a MVC triad where the model needs to be shared between the controller and view, but if another instance of the controller and view are created, they need a new instance of their model shared between them.

The best way to explain this is a practical demonstration:

class {

    public 
$b$c;

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

}


class 
{
    public 
$d;

    public function 
__construct(D $d) {
        
$this->$d;
    }
}

class 
{
    public 
$d;

    public function 
__construct(D $d) {
        
$this->$d;
    }
}


class 
{}

By using shareInstances it's possible to mark D as shared within each instance of an object tree. The important distinction between this and global shared objects is that this object is only shared within a single instance of the object tree.

$dice $dice->addRules([
    
'A' => [
        
'shareInstances' => ['D']
    ]
]);


//Create an A object
$a $dice->create('A');

//Anywhere that asks for an instance D within the tree that existis within A will be given the same instance:
//Both the B and C objects within the tree will share an instance of D
var_dump($a->b->=== $a->c->d); //TRUE

//However, create another instance of A and everything in this tree will get its own instance of D:

$a2 $dice->create('A');
var_dump($a2->b->=== $a2->c->d); //TRUE

var_dump($a->b->=== $a2->b->d); //FALSE
var_dump($a->c->=== $a2->c->d); //FALSE

4 Advanced features

4.1 Rule Updating

This is a new feature for Dice 2.0

When adding a rule that has already been set, Dice will update the existing rule that is applied to that class

$dice $dice->addRules([
    
'B' => [
        
'shared' => true
    
]
]);

$dice $dice->addRules([
    
'B' => [
        
'constructParams' => ['foo']
    ]
]);

In Dice 1.x This would have overridden the first rule with the second, B would have shared set to false and constructParams set.

As of Dice 2.0, both rules will be applied to the B class.

Where this is useful is when using inheritance

class {

}

class 
extends {

}
$dice $dice->addRules([
    
'A' => [
        
'shared' => true
    
]
]);

$dice $dice->addRules([
    
'B' => [
        
'constructParams' => ['foo']
    ]
]);

Because B inherits A, rules applied to A will applied to B (this behaviour can be turned off, see the section on inheritance) so in this instance, B will be both shared and have the constructor parameters set.

However if required, shared can be turned off for B:

$dice $dice->addRules([
    
'A' => [
        
'shared' => true
    
]
]);

$dice $dice->addRules([
    
'B' => [
        
'constructParams' => ['foo'],
        
'shared' => false
    
]
]);

And this keep A shared, but turn it off for the subclass B.

4.2 Namespace support

As of 28/02/2014 Dice fully supports working with namespaces. However, there is one caveat, you should use double backslashes throughout so not to treat them as escape characters in PHP strings:

For example:

$rule = [

];

$dice $dice->addRules([
    
'Foo\\A' => [
        
'substitutions' => [
            
'Foo\\B' => 'Foo\\ExtendedB'
        
]
    ]
]);

$a $dice->create('Foo\\A');

All references to classnames in instances, substitutions and any other configuration option must contain the full namespace information but no leading slash.

N.b. because the \ character is an escape character in strings it must be double escaped, which is why these examples show Foo\\Bar instead of Foo\Bar

As of PHP 5.5, PHP supports a static ::class constant on all classes which returns the fully qualified namesapace. This heavily helps reduce verbosity when using long namespaces in Dice. Consider the following:

$dice $dice->addRules([
    
'Maphper\\Maphper' => [
        
'substitutions' =>
            [
'Maphper\\DataSource' => 'Maphper\\DataSource\\Database']
    ]
]);

If you use the class then verbosity can be reduced

use Maphper\Maphper;
use 
Maphper\DataSource;
use 
Maphper\DataSource\Database;

$dice $dice->addRules([
    
Maphper::class => [
        
'substitutions' => [DataSource::class => Database::class]
    ]
]);

4.4 Referecning other instances with Dice::INSTANCE

You may wish to reference instances from the container in your configuration. You can do this using an array with the key \Dice\Dice::INSTANCE. For example:

class {

    
//Note the missing type hint, this will have
    //to be set with constructParams
    
public function __construct($db) {

    }
}

$dice $dice->addRules([
    
'A' => [
        
'constructParams' => [
            [\
Dice\Dice::INSTANCE => 'PDO']
        ]
    ]

])

This is the equivalent of:

new A($dice->create('PDO'));

Dice::INSTANCE can be used anywhere in dice rules. constructParams, substitutions, call, etc.

$dice $dice->addRules([
    
'B' => [
        
'call' => [
            [
                
'setDb' => [ [\Dice\Dice::INSTANCE => 'PDO'] ]
            ]
        ]
    ]

]);

//equivalent to
$b = new B;
$b->setDb($dice->create('PDO'));

4.5 Constants

Note: This is most useful for JSON configuration.

Some classes require constants for configuration. For example, to turn on exceptions in PDO you must use the code:

$pdo->setAttribute(PDO::ATTR_ERRMODEPDO::ERRMODE_EXCEPTION);

If you are using PHP configuration this is easy, however for JSON is a little more difficult. Dice supports using the Dice::CONSTANT constant to reference PHP constants.

Note: this example uses JSON configuration as it's not required when using pure PHP

{
    
"PDO": {
        
"constructParams": [
            
"mysql:host=host;dbname=name",
            
"user",
            
"pass"
        
],
        
"call": [
            [
                
"setAttribute",
                [
                    {
'Dice::CONSTANT' => 'PDO::ATTR_ERRMODE'},
                    {
'Dice::CONSTANT' => 'PDO::ERRMODE_EXCEPTION'}
                ]
            ]

        ]
    }
}

4.6 Global Variables

Note: This is most useful for JSON configuration.

Some classes require access to global variables (e.g. superglobals like $_GET, $_POST and $_SERVER)

Global variables can be used as constructor arguments using the Dice::GLOBAL constant:

Note: this example uses JSON configuration as it's not required when using pure PHP

class AppKernel {
    public function 
__construct($get$post$server) {

    }
}
{
    
"AppKernel": {
        
"constructParams": [
            {
"Dice::GLOBAL""_GET"},
            {
"Dice::GLOBAL""_POST"},
            {
"Dice::GLOBAL""_SERVER"}
        ]
    }
}

4.7 Method Chaining

Consider the following Object:

$httpRequest = new HTTPRequest();
$httpRequest $httpRequest->url('http://example.org')->method('POST')->postdata('foo=bar');

As of 4.0 Dice supports chaining method call using the call rule and the Dice::CHAIN_CALL constant:

$dice $dice->addRules([
    
'HTTPRequest' => [
        
'call' => [
            [
'url', ['http://example.org'], Dice::CHAIN_CALL],
            [
'method', ['POST'], Dice::CHAIN_CALL ],
            [
'postdata', ['foo=bar'], Dice::CHAIN_CALL]
        ]
    ]
]);

Dice will replace the HTTPRequest object with the result of the chained call and return the last object in the chain when the object is requested from the container. This is also useful for factories:

$dice $dice->addRules([
    
'MyDatabase' => [
        
'instanceOf' => 'DatabaseFactory',
        
'call' => [
            [
'get', ['Database'], Dice::CHAIN_CALL]
        ]
    ]
]);

$database $dice->create('MyDatabase');


//Equivalent of:
$factory = new DatabaseFactory();
$database $factory->get('Database');

Defining Dice Rules using JSON

To enable a better separation of concerns as well as a cleaner method of configuration, Dice supports defining rules using JSON. Dice Rules can be completely configured using JSON and has all the power that defining rules in PHP does.

It is recommended that you use JSON for your rules instead of PHP

Basic usage:

Using a path to a file

$dice = new \Dice\Dice;
$dice $dice->addRules('rules.json');

Using an existing JSON string

$json json_decode(file_get_contents('rules.json'));

$dice = new \Dice\Dice;
$dice $dice->addRules($json);

Basic usage

Every rule can be defined using JSON. Here is a complete JSON file with every rule. Note that Dice constants should be expressed as strings:

{
    
"ClassName": {
        
"shared"true,
        
"instanceOf""InstanceName",
        
"call": [
            
"method1",  ["arg1""arg2"],
            
"method2",  ["arg1""arg2"]
        ],
        
"substitutions": [
            
"PDO": {"Dice::INSTANCE""MyPDO"}
        ],
        
"constructParams": [
            
"foo",
            {
"Dice::GLOBAL""_POST"},
            {
"Dice::GLOBAL""_GET"},
            {
"Dice::GLOBAL""_SERVER"}
        ],
        
"inherit"false
    
},

    
"NextClass": {
        
"shared"true
    
},

    
"anotherClass": {
        
"inherit"false
    
}
}

Class API

Dice

  • create($component [, array $constructorParameters])
  • addRules(array $rules)
  • addRules($name, array $rule)
  • getRule($name)

FAQs

  1. The documentation isn't clear/I don't understand something/How can I ....?

    If you have a question about something on this page which isn't clear please email me: tom@r.je or post a question over at GitHub. I can improve the documentation for everyone!

  2. Will Dice support @Inject annotations or similar?

    No. Putting dependency rules back into the application code defeats the purpose of using a Dependency Injection Container in the first place. Please see my arguments against using annotation based configuration for a complete answer to why annotations are not a suitable candidate for this job.

  3. Will Dice ever support [feature]?

    Probably not. Dice is designed to be minimal and lightweight. It loosely follows the X.org design principles; specifically Do not add new functionality unless an implementor cannot complete a real application without it.. However, if a feature is is genuinely useful and not repeating something which is already possible, please request it on github.

    Features such as additional configuration syntaxes may be added but these will be optional and not part of Dice's core.

  4. I'd like to report a bug or request a feature

    Please do this over at github: https://github.com/Level-2/Dice

  5. What is the licence of Dice?

    Dice uses the BSD Licence: http://opensource.org/licenses/bsd-license.php

  6. Can I distribute Dice with my project?

    Yes. Just leave the copyright notice intact.

  7. Can I use Dice for a commercial application?

    Yes. Just leave the copyright notice intact.

  8. I have another question

    Please email me: tom@r.je with any additional queries.