Tom Butler's programming blog

Dice - PHP Dependency Injection Container

Introduction

Last updated 14/05/2015

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:

PHP Code:

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

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

PHP Code:

$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:

PHP Code:

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:

PHP Code:

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:

PHP Code:

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:

PHP Code:

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

And require it in one of your existing classes:

PHP Code:

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. Override shared dependencies
    5. Setter injection
    6. Default rules
    7. Named instances
    8. Sharing instances for a single tree
  4. Additional features
    1. Callbacks
    2. Rule cascading
    3. Namespace support
  5. Defining rules using XML
    1. Basic usage
    2. Additional features enabled in the XML loader

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

PHP Code:

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:

PHP Code:

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:

PHP Code:

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:

PHP Code:

$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.

PHP Code:

//create a rule to apply to shared object $rule = ['shared' => true]; //Apply the rule to instances of PDO $dice->addRule('PDO'$rule); //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:

PHP Code:

$rule = [          //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->addRule('PDO'$rule); //Now any time PDO is requested from Dice, the same instance will be returned //And will havebeen 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:

PHP Code:

$dice = new \Dice\Dice; $rule = ['name' => 'value']; $dice->addRule('rulename'$rule);

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

PHP Code:

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

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

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 when the class name is not passed to $dice->addRule(). 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:

PHP Code:

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:

PHP Code:

class implements Iterator {     //... }

The rule can be defined like this:

PHP Code:

//When a constructor asks for an instance of Iterator pass it an instance of B instead $rule = ['substitutions' => ['Iterator' => ['instance' => 'B']]]; $dice->addRule('A'$rule); $a $dice->create('A');

The ['instance' => 'name'] syntax is used to tell Dice to create an instance of 'B' in place of 'Iterator'. ['instance' => 'B'] can be read as 'An instance of B created by the container'.

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

However, what if If the application required this?

PHP Code:

$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:

PHP Code:

$rule = ['substitutions' => ['Iterator' => new DirectoryIterator('/tmp')]]; $dice->addRule('A'$rule); $a $dice->create('A');

2. factory substitution with closures

You can pass a closure into the ['instance'] array and it will be called and the return value will be used as the substitution when it's required. Please note this is done just-in-time so will be called as the class it's been applied to is instantiated.

PHP Code:

$rule = ['substitutions' =>              ['Iterator' => ['instance' => function() {                             return new DirectoryIterator('/tmp');                         }]             ]         ]; $dice->addRule('A'$rule); $a $dice->create('A');

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

PHP Code:

$namedDirectoryIteratorRule = [                     //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'] ]; //Create a rule under the name "$MyDirectoryIterator" which can be referenced as a substitution for any other rule $dice->addRule('$MyDirectoryIterator'$namedDirectoryIteratorRule); //This tells the DI Container to use the configuration for $MyDirectoryIterator when an Iterator is asked for in the constructor argument $aRule = ['substitutions' =>              [                 'Iterator' => ['instance' => '$MyDirectoryIterator']             ]         ]; //Apply the rule to the A class $dice->addRule('A'$aRule); //Now, when $a is created, it will be passed the Iterator configured as $MyDirectoryIterator $a $dice->create('A');

3.2 Inheritance

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

PHP Code:

class { } class extends { } //Mark instances of A as shared $aRule = ['shared' => true]; $dice->addRule('A'$aRule); //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:

PHP Code:

class { } class extends { } //This time mark A as shared, but turn off rule inheritance $aRule = ['shared' => true'inherit' => false]; $dice->addRule('A'$rule); $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:

PHP Code:

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:

PHP Code:

$rule = ['constructParams' => ['Foo''Bar']]; $dice->addRule('A'$rule); $a $dice->create('A');

This is equivalent to:

PHP Code:

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

Constructor parameter order for dependencies does not matter:

PHP Code:

class {     public function __construct($foo$barB $b) {     } } $rule = ['constructParams' => ['Foo''Bar']]; $dice->addRule('A'$rule); $a $dice->create('A')

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

PHP Code:

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:

PHP Code:

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";     } } $rule = [             'call' => [                 ['method1', ['Foo1' ,'Bar1']],                 ['method1', ['Foo2' ,'Bar2']],                 ['method2', []]             ]         ]; $dice->addRule('A'$rule); $a $dice->create('A');

This will output:

PHP Code:

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

The methods defined in $rule['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.

PHP Code:

$rule = [     'constructParams' => ['mysql:host=127.0.0.1;dbname=mydb''username''password'],     'shared' true,     'call' => [         ['setAttribute', [PDO::ATTR_DEFAULT_FETCH_MODEPDO::FETCH_OBJ]]     ] ]; $dice->addRule('PDO'$rule); 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:

PHP Code:

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:

PHP Code:

class Foo {     public $pdo;          public function __construct(PDO $pdo) {         $this->pdo $pdo;     } } //When PDO is type hinted, supply an instance of MyPDO instead $rule = ['substitutions' => ['PDO' => ['instance' => 'MyPDO']]]; //Apply the rule to every class $dice->addRule('*'$rule); $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:

PHP Code:

class DataCopier {     public function __construct(PDO $database1PDO $database2) {              } } //A rule for the default PDO object $rule = [     'shared' => true,     'constructParams' = ['mysql:host=127.0.0.1;dbname=mydb''username''password'] ]; $dice->addRule('PDO'$rule); //And a rule for the second database $secondDBRule = [     'shared' => true,     'constructParams' = ['mysql:host=externaldatabase.com;dbname=foo''theusername''thepassword'],          //This rule will create an instance of the PDO class     'instanceOf' => 'PDO' ]; //Add named instance called $Database2 //Notice that the name being applied to is not the name of class //but a chosen named instance $dice->addRule('$Database2'$secondDBRule); //Now set DataCopier to use the two different databases: $dataCopierRule = [ ]; //Set the constructor parameters to the two database instances. $dataCopierRule->constructParams = [          ['instance' => 'PDO'],         ['instance' => '$Database2'] ]; $dice->addRule('DataCopier'$dataCopierRule); $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 ['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.

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 class 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:

PHP Code:

class {     public $b$c;          public function __construct(B $bC $c) {               } } class {     public $d;          public function __construct(D $d) {         $this->$d;     } } class {     public $d;          public function __construct(D $d) {         $this->$d;     } } class {} By using $rule->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.

PHP Code:

$rule = [     'shareInstances' = ['D'] ]; $dice->addRule('A'$rule); //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_dumb($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_dumb($a2->b->=== $a2->c->d); //TRUE var_dumb($a->b->=== $a2->b->d); //FALSE var_dumb($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

PHP Code:

$dice->addRule('B', ['shared' => true]); $dice->addRule('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

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

Where this is useful is when using inheritance

PHP Code:

class { } class extends { }

PHP Code:

$dice->addRule('A', ['shared' => true]); $dice->addRule('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:

PHP Code:

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

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

4.3 Namespace support

As of 28/02/2014 Dice fully supports working with namespaces. However, there is one caveat: All configuration must be done with fully qualified class names and without the first backslash.

For example:

PHP Code:

$rule = [     'substitutions' => [         'Foo\\B' => 'Foo\\ExtendedB'      ] ]; $dice->addRule('Foo\\A'$rule); $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 PHP5.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:

PHP Code:

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

If you use the class then verbosity can be reduced

PHP Code:

use Maphper\Maphper; use Maphper\DataSource; use Maphper\DataSource\DataBase; $dice->addRule(Maphper::class, [     'substitutions' => [DataSource::class => DataBase::class] ]);

Defining DiceRules using XML

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

Basic usage:

Using a path to a file

PHP Code:

$dice = new \Dice\Dice; $xmlLoader = new \Dice\XmlLoader; $xmlLoader->loadXml('dice.xml'$dice);

Using a SimpleXmlElement

PHP Code:

$dice = new \Dice\Dice; $xmlLoader = new \Dice\XmlLoader; $xmlLoader->loadXml(simplexml_load_file('dice.xml'), $dice);

Note: The XML Loader can take either a path to an XML file as a string or a SimpleXmlElement. This is by design, it allow applications to potentially store Dice configuration along with other XML based application metadata in the same file. By not forcing application developers to create a file specifically for Dice congiguration, it's able to offer greater flexibility.

Basic usage

Every rule can be defined using XML. Here is a complete XML file. With the exception of <name> every tag is optional.

<?xml version="1.0"?>
<dice xmlns="https://r.je/dice/2.0">
  <!-- Required Name attrbiute + optional Attributes for: -->
  <!-- Shared: either true or false. If the tag is omitted it defaults to false -->
  <!-- Inherit: whether the rule will also be applied to subclasses -->
  <!-- InstanceOf: When using named instances, the name of the class to use -->
  <rule name="Foo" shared="true" inherit="false" instanceOf="Bar">
    <!-- Multiple substitutions can be defined using the substitute tag: -->
    <substitute use="BIterator" as="Iterator"/>
    <substitute use="X" as="Y"/>
    <!-- Supports calling parameters after construction using the call tag: -->
    <call method="setAttribute">
      <param>Foo</param>
      <param>Bar</param>
    </call>
    <!-- Multiple call tags can be defined. Instances of a specified class name are
        supported by using the <instance> tag. This will supply an instance of "X" 
        equivilant to $dice->create('X'); (using rules for X) to the setDependency method.
        Instance supports named instances as well as class names.
        -->
    <call method="setDependency">
      <instance>X</instance>
    </call>
    <!-- List of constructor parameters -->
    <constructParams>
      <param>123</param>
      <param>XYZ</param>
    </constructParams>
  </rule>
  <rule>
    <name>Bar</name>
    <!-- Define rules for the next component -->
  </rule>
</dice>

5.2 Additional Features

The XML loader supports several additional features designed to make the usage easier and compete with using PHP for flexibility.

Firstly it supports reading of values from existing classes

<?xml version="1.0"?>
<dice>
  <rule>
    <name>Config</name>
    <shared>true</shared>
  </rule>
  <rule>
    <name>PDO</name>
    <shared>true</shared>
    <construct>
      <param>{Config::dbServer}</param>
      <param>{Config::dbUser}</param>
      <param>{Config::dbPassword}</param>
    </construct>
  </rule>
</dice>

{Config::dbServer} is evaluated as the value from $dice->create('Config')->dbServer; Allowing you to reference instances of classes within the XML file. It follows existing rules, so where Config is marked as shared, it will read from a shared instance of config.

The second feature supported is method calling, useful for factories:

<?xml version="1.0"?>
<dice>
  <rule>
    <name>Config</name>
    <shared>true</shared>
  </rule>
  <rule>
    <name>Y</name>
    <construct>
      <param>{XFactory::create(x.xml)}</param>
    </construct>
  </rule>
</dice>

Each time Y is created, it will be passed the result of $dice->create('XFactory')->create('x.xml') as its first parameter

Class API

Dice

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

DiceXmlLoader

  • loadXml(SimpleXmlElement $rules, Dice $dice)
  • loadXml(String $xmlFileName, Dice $dice)

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/TomBZombie/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.