Tom Butler's programming blog

Dice - PHP Dependency Injection Container

Introduction

Last updated 28/02/2014

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 and contained within a single file.
  • 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 $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.

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: 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 without worrying about 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, 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 any configuration of where it gets its dependencies from. 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 DiceRules
    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 DiceRules 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 $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 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.

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

PHP Code:

//create a rule and flag it as shared. $rule = new \Dice\Rule; $rule->shared true; //Apply the rule to instances of PDO $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); //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:

//Firstly create a rule $rule = new \Dice\Rule; $rule->shared true; //PDO will be constructed by the container with these parameters: $rule->constructParams = ['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); //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 DiceRules

In order to allow complete flexibility, the container can be fully configured using rules provided by instances of the DiceRule class. Rules are passed to the container using:

PHP Code:

$dice = new \Dice $rule = new \Dice\Rule; $dice->addRule('rulename'$rule);

By default, rule names match class names so, to set the rule for 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

DiceRules 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
  • $newInstances (array) - A list of dependencies which will always be passed as new instances, ignoring $shared. 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

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:

$rule = new \Dice\Rule; $rule->substitutions['Iterator'] = new \Dice\Instance('B'); $dice->addRule('A'$rule); $a $dice->create('A');

The DiceInstance class is used. This tells the DIC to create an instance of 'B' in place of 'Iterator'. new \Dice\Instance('B') can be read as 'An instance of B created by the injection container'.

The reason that $rule->substitutions['Iterator'] = $dice->create('B') is not used is that this creates a B object there and then. Using DiceInstance 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 two ways this can be achieved using Dice.

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

PHP Code:

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

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

PHP Code:

$namedDirectoryIteratorRule = new \Dice\Rule; $namedDirectoryIteratorRule->instanceOf 'DirectoryIterator'; $namedDirectoryIteratorRule->constructParams = ['/tmp']; //Create a rule under the name "$MyDirectoryIterator" which can be referenced as a substitution for any other rule $dice->addRule('$MyDirectoryIterator'$namedDirectoryIteratorRule); $rule = new \Dice\Rule; //This tells the DI Container to use the configuration for $MyDirectoryIterator whenever it creates an instance of A: $rule->substitutions['Iterator'] = new \Dice\Instance('$MyDirectoryIterator'); $dice->addRule('A'$rule); $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 { } $aRule = new \Dice\Rule; $aRule->shared true; $dice->addRule('A'$aRule); $bRule $dice->getRule('B'); var_dump($bRule === $aRule); //TRUE var_dump($bRule->shared); //TRUE

DiceRule::$inherit can be used to disable this behaviour:

PHP Code:

class { } class extends { } $aRule = new \Dice\Rule; $aRule->shared true; $aRule->inherit false; $dice->addRule('A'$rule); $bRule $dice->getRule('B'); var_dump($bRule === $aRule); //FALSE var_dump($bRule->shared); //FALSE

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

These are supplied using:

PHP Code:

$rule = new \Dice\Rule; $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 = new \Dice\Rule; $rule->constructParams = ['Foo''Bar']; $dice->addRule('A'$rule); $a $dice->create('A')

Will execute as expected and be equal to:

PHP Code:

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

3.4 Override shared dependencies

It's possible to ignore the shared rule and force a new instance of any dependiecies by using newInstances

PHP Code:

class {     public $b$c$d;          public function __construct(B $bC $cD $d) {         $this->$b;             $this->$c;             $this->$d;         } } $rule = new \Dice\Rule; $rule->shared true; //Make B,C and D shared $dice->addRule('B'$rule); $dice->addRule('C'$rule); $dice->addRule('D'$rule); $rule = new \Dice\Rule; $rule->newInstances = ['B''C']; $dice->addRule('A'$rule); $a $dice->create('A'); //Although B,C and D are shared, this rule is ignored for instances of 'A' and it only uses the shared instance of 'D' var_dump($a->== $dice->create('B')); //FALSE var_dump($a->== $dice->create('C')); //FALSE var_dump($a->== $dice->create('D')); //TRUE

3.5 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, DiceRules 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 = new \Dice\Rule; $rule->call[] = ['method1', ['Foo1' ,'Bar1']]; $rule->call[] = ['method1', ['Foo2' ,'Bar2']]; $rule->call[] = ['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 DiceRule::$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 = new \Dice\Rule; $rule->constructParams = ['mysql:host=127.0.0.1;dbname=mydb''username''password']; $rule->shared true; $rule->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.6 Default rules

Dice also allows for a rule to apply to any object it creates by setting the name 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;     } } $rule = new \Dice\Rule; $rule->substitutions['PDO'] = new \Dice\Instance('MyPDO'); $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.7 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 = new \Dice\Rule; $rule->shared true; $rule->constructParams = ['mysql:host=127.0.0.1;dbname=mydb''username''password']; $dice->addRule('PDO'$rule); //And a rule for the second database $secondDBRule = new \Dice\Rule; $secondDBRule->shared true; $secondDBRule->constructParams = ['mysql:host=example.com;dbname=externaldatabase''foo''bar']; //Set it to an instance of PDO $secondDBRule->instanceOf 'PDO'; //Add named instance called $Database2 $dice->addRule('$Database2'$secondDBRule); //Now set DataCopier to use the two different databases: $dataCopierRule = new \Dice\Rule; //Set the constructor parameters to the two database instances. $dataCopierRule->constructParams = [new \Dice\Instance('PDO'), new \Dice\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 new \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.

3.8 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. 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 = new \Dice\Rule; $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 Callbacks

Please note: This feature was removed in Dice 1.2 as there is no real use-case for the feature. The original use-case was to allow external code to track injected dependencies to pass them back into Dice. This functionality is now availalbe using $rule->shareInstances so this feature has been removed.

It can be useful for high level parts of the application to know exactly which dependencies an object has been passed as it's created. Dice allows the developer to get a list of all the dependencies which were given to an objects constructor. This is possible using the $callback parameter in $dice->create();

PHP Code:

class {     public function __construct(B $bC $c) {                  } } $dice->create('A', [], function($args) {     var_dump(get_class($args[0]); // "B"     var_dump(get_class($args[1]); // "C" });

The callback returns the exact parameters that have been passed to A following any rules which have been supplied to the container.

4.2 Rule cascading

In most cases it's useful to extend current rules rather than completely overwrite them. For instance, when adding an arbitrary rule it would be useful to inherit from the default rule instead of writing a new one for a given class. For example:

PHP Code:

$rule = new \Dice\Rule; $rule->shared true; $dice->addRule('*'$rule); $rule2 = new \Dice\Rule; $rule2->constructParams = [1,2]; $dice->addRule('Foo'$rule2);

In this example, although the default rule says that everything is shared. Instances of Foo will not be because the rule has been overwritten.

However, Dice's getRule() method allows you to get the rule which is currently applied to a class:

PHP Code:

$rule = new \Dice\Rule; $rule->shared true; $dice->addRule('*'$rule); $rule2 $dice->getRule('Foo');

In this example, $rule2 will be the default rule. To allow cascading, simply clone it and make amends before saving it back as the rule for Foo

PHP Code:

$rule = new \Dice\Rule; $rule->shared true; $dice->addRule('*'$rule); $rule2 = clone $dice->getRule('Foo'); $rule2->constructParams = [1,2]; $dice->addRule('Foo'$rule2);

The rule for foo now inherits the default rule and Foo is shared as well as having its own constructor parameters.

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 = new \Dice\Rule; $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

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 (and more) that defining DiceRules in PHP does.

Basic usage:

Using a path to a file

PHP Code:

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

Using a SimpleXmlElement

PHP Code:

$dice = new \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>
  <rule>
    <name>Foo</name>
    <!-- Shared: either true or false. If the tag is omitted it defaults to false -->
    <shared>true</shared>
    <!-- Multiple substitutions can be defined using the substitute tag: -->
    <substitute>
      <use>BIterator</use>
      <as>Iterator</as>
    </substitute>
    <substitute>
      <use>X</use>
      <as>Y</as>
    </substitute>
    <!-- Supports calling parameters after construction using the call tag: -->
    <call>
      <method>setAttribute</method>
      <params>
        <param>Foo</param>
        <param>Bar</param>
      </params>
    </call>
    <call>
      <method>setAttribute</method>
      <params>
        <param>Bar</param>
        <param>Baz</param>
      </params>
    </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</method>
      <params>
        <instance>X</instance>
      </params>
    </call>
    <!-- For named instances, supports the instanceof tag. 
             See documentation on Named Instances 
        -->
    <instanceof>Bar</instanceof>
    <!-- Comma separated list of dependencies which will be created new, ignoring their shared rules -->
    <newinstances>Foo,Bar</newinstances>
    <!-- List of constructor parameters -->
    <construct>
      <param>123</param>
      <param>XYZ</param>
    </construct>
  </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, bool $forceNewInstance])
  • addRule($name, DiceRule $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.