Skip to content

Commit

Permalink
Merge pull request #138 from Level-2/3.0-Dev-Rebase
Browse files Browse the repository at this point in the history
3.0 dev rebase
  • Loading branch information
TRPB authored Apr 8, 2018
2 parents c3a480e + 5ff15b5 commit 9fc016a
Show file tree
Hide file tree
Showing 26 changed files with 568 additions and 681 deletions.
66 changes: 43 additions & 23 deletions Dice.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
<?php
/* @description Dice - A minimal Dependency Injection Container for PHP *
* @author Tom Butler [email protected] *
* @copyright 2012-2015 Tom Butler <[email protected]> | https:// r.je/dice.html *
* @copyright 2012-2018 Tom Butler <[email protected]> | https:// r.je/dice.html *
* @license http:// www.opensource.org/licenses/bsd-license.php BSD License *
* @version 2.0 */
* @version 3.0 */
namespace Dice;
class Dice {
const CONSTANT = 'Dice::CONSTANT';
const GLOBAL = 'Dice::GLOBAL';
const INSTANCE = 'Dice::INSTANCE';
/**
* @var array $rules Rules which have been set using addRule()
*/
Expand All @@ -26,17 +29,30 @@ class Dice {
* @param string $name The name of the class to add the rule for
* @param array $rule The container can be fully configured using rules provided by associative arrays. See {@link https://r.je/dice.html#example3} for a description of the rules.
*/
public function addRule($name, array $rule) {
if (isset($rule['instanceOf']) && (!array_key_exists('inherit', $rule) || $rule['inherit'] === true )) $rule = array_replace_recursive($this->getRule($rule['instanceOf']), $rule);
public function addRule(string $name, array $rule) {
if (isset($rule['instanceOf']) && (!array_key_exists('inherit', $rule) || $rule['inherit'] === true )) {
$rule = array_replace_recursive($this->getRule($rule['instanceOf']), $rule);
}
//Allow substitutions rules to be defined with a leading a slash
if (isset($rule['substitutions'])) foreach($rule['substitutions'] as $key => $value) $rule[ltrim($key, '\\')] = $value;

$this->rules[ltrim(strtolower($name), '\\')] = array_replace_recursive($this->getRule($name), $rule);
}

/**
* Add rules as array. Useful for JSON loading $dice->addRules(json_decode(file_get_contents('foo.json'));
* @param array Rules in a single array [name => $rule] format
*/
public function addRules(array $rules) {
foreach ($rules as $name => $rule) $this->addRule($name, $rule);
}

/**
* Returns the rule that will be applied to the class $name when calling create()
* @param string name The name of the class to get the rules for
* @return array The rules for the specified class
*/
public function getRule($name) {
public function getRule(string $name): array {
$lcName = strtolower(ltrim($name, '\\'));
if (isset($this->rules[$lcName])) return $this->rules[$lcName];

Expand All @@ -58,7 +74,7 @@ public function getRule($name) {
* @param array $share Whether or not this class instance be shared, so that the same instance is passed around each time
* @return object A fully constructed object based on the specified input arguments
*/
public function create($name, array $args = [], array $share = []) {
public function create(string $name, array $args = [], array $share = []) {
// Is there a shared instance set? Return it. Better here than a closure for this, calling a closure is slower.
if (!empty($this->instances[$name])) return $this->instances[$name];

Expand All @@ -75,7 +91,7 @@ public function create($name, array $args = [], array $share = []) {
* @param array $rule The container can be fully configured using rules provided by associative arrays. See {@link https://r.je/dice.html#example3} for a description of the rules.
* @return callable A closure
*/
private function getClosure($name, array $rule) {
private function getClosure(string $name, array $rule) {
// Reflect the class and constructor, this should only ever be done once per class and get cached
$class = new \ReflectionClass(isset($rule['instanceOf']) ? $rule['instanceOf'] : $name);
$constructor = $class->getConstructor();
Expand Down Expand Up @@ -114,8 +130,8 @@ private function getClosure($name, array $rule) {
$object = $closure($args, $share);

foreach ($rule['call'] as $call) {
// Generate the method arguments using getParams() and call the returned closure (in php7 will be ()() rather than __invoke)
$params = $this->getParams($class->getMethod($call[0]), ['shareInstances' => isset($rule['shareInstances']) ? $rule['shareInstances'] : [] ])->__invoke($this->expand(isset($call[1]) ? $call[1] : []));
// Generate the method arguments using getParams() and call the returned closure
$params = $this->getParams($class->getMethod($call[0]), ['shareInstances' => isset($rule['shareInstances']) ? $rule['shareInstances'] : [] ])(($this->expand(isset($call[1]) ? $call[1] : [])));
$return = $object->{$call[0]}(...$params);
if (isset($call[2]) && is_callable($call[2])) call_user_func($call[2], $return);
}
Expand All @@ -124,24 +140,29 @@ private function getClosure($name, array $rule) {
}

/**
* Looks for 'instance' array keys in $param and when found returns an object based on the value see {@link https:// r.je/dice.html#example3-1}
* Looks for Dice::INSTANCE, Dice::GLOBAL or Dice::CONSTANT array keys in $param and when found returns an object based on the value see {@link https:// r.je/dice.html#example3-1}
* @param mixed $param Either a string or an array,
* @param array $share Whether or not this class instance be shared, so that the same instance is passed around each time
* @param array $share Array of instances from 'shareInstances', required for calls to `create`
* @param bool $createFromString
* @return mixed
*/
private function expand($param, array $share = [], $createFromString = false) {
if (is_array($param) && isset($param['instance'])) {
// Call or return the value sored under the key 'instance'
// For ['instance' => ['className', 'methodName'] construct the instance before calling it
$args = isset($param['params']) ? $this->expand($param['params']) : [];
if (is_array($param['instance'])) $param['instance'][0] = $this->expand($param['instance'][0], $share, true);
if (is_callable($param['instance'])) return call_user_func($param['instance'], ...$args);
else return $this->create($param['instance'], array_merge($args, $share));
private function expand($param, array $share = [], bool $createFromString = false) {
if (is_array($param)) {
//if a rule specifies Dice::INSTANCE, look up the relevant instance
if (isset($param[self::INSTANCE])) {
//Check for 'params' which allows parameters to be sent to the instance when it's created
//Either as a callback method or to the constructor of the instance
$args = isset($param['params']) ? $this->expand($param['params']) : [];

//Support Dice::INSTANCE by creating/fetching the specified instance
if (is_callable($param[self::INSTANCE])) return call_user_func($param[self::INSTANCE], ...$args);
else return $this->create($param[self::INSTANCE], array_merge($args, $share));
}
else if (isset($param[self::GLOBAL])) return $GLOBALS[$param[self::GLOBAL]];
else if (isset($param[self::CONSTANT])) return constant($param[self::CONSTANT]);
else foreach ($param as $name => $value) $param[$name] = $this->expand($value, $share);
}
// Recursively search for 'instance' keys in $param
else if (is_array($param)) foreach ($param as $name => $value) $param[$name] = $this->expand($value, $share);
// 'instance' wasn't found, return the value unchanged

return is_string($param) && $createFromString ? $this->create($param) : $param;
}

Expand Down Expand Up @@ -182,7 +203,6 @@ private function getParams(\ReflectionMethod $method, array $rule) {
$parameters[] = $sub ? $this->expand($rule['substitutions'][$class], $share, true) : $this->create($class, [], $share);
}
catch (\InvalidArgumentException $e) {

}
// For variadic parameters, provide remaining $args
else if ($param->isVariadic()) $parameters = array_merge($parameters, $args);
Expand Down
33 changes: 0 additions & 33 deletions Loader/Json.php

This file was deleted.

18 changes: 8 additions & 10 deletions Loader/Xml.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
<?php
/* @description Dice - A minimal Dependency Injection Container for PHP
* @author Tom Butler [email protected]
* @copyright 2012-2014 Tom Butler <[email protected]>
* @link http://r.je/dice.html
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @version 2.0
*/
<?php
/* @description Dice - A minimal Dependency Injection Container for PHP *
* @author Tom Butler [email protected] *
* @copyright 2012-2018 Tom Butler <[email protected]> | https:// r.je/dice.html *
* @license http:// www.opensource.org/licenses/bsd-license.php BSD License *
* @version 3.0 */
namespace Dice\Loader;
class Xml {
private function getComponent(\SimpleXmlElement $element, $forceInstance = false) {
if ($forceInstance) return ['instance' => (string) $element];
else if ($element->instance) return ['instance' => (string) $element->instance];
if ($forceInstance) return [\Dice\Dice::INSTANCE => (string) $element];
else if ($element->instance) return [\Dice\Dice::INSTANCE => (string) $element->instance];
else return (string) $element;
}

Expand Down
101 changes: 93 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Simple example:
<?php
class A {
public $b;

public function __construct(B $b) {
$this->b = $b;
}
Expand Down Expand Up @@ -63,7 +63,7 @@ Dice is compatible with PHP5.4 and up, there are archived versions of Dice which
Performance
-----------

Dice uses reflection which is often wrongly labelled "slow". Reflection is considerably faster than loading and parsing a configuration file. There are a set of benchmarks [here](https://rawgit.com/TomBZombie/php-dependency-injection-benchmarks/master/test1-5_results.html) and [here](https://rawgit.com/TomBZombie/php-dependency-injection-benchmarks/master/test6_results.html) (To download the benchmark tool yourself see [this repository](https://github.com/TomBZombie/php-dependency-injection-benchmarks)) and Dice is faster than the others in most cases.
Dice uses reflection which is often wrongly labelled "slow". Reflection is considerably faster than loading and parsing a configuration file. There are a set of benchmarks [here](https://rawgit.com/TomBZombie/php-dependency-injection-benchmarks/master/test1-5_results.html) and [here](https://rawgit.com/TomBZombie/php-dependency-injection-benchmarks/master/test6_results.html) (To download the benchmark tool yourself see [this repository](https://github.com/TomBZombie/php-dependency-injection-benchmarks)) and Dice is faster than the others in most cases.

In the real world test ([test 6](https://rawgit.com/TomBZombie/php-dependency-injection-benchmarks/master/test6_results.html)) Dice is neck-and-neck with Pimple (which requires writing an awful lot of configuration code) and although Symfony\DependencyInjection is faster at creating objects, it has a larger overhead and you need to create over 500 objects on each page load until it becomes faster than Dice. The same is true of Phalcon, the overhead of loading the Phalcon extension means that unless you're creating well over a thousand objects per HTTP request, the overhead is not worthwhile.

Expand All @@ -77,6 +77,91 @@ Originally developed by Tom Butler (@TomBZombie), with many thanks to daniel-mei
Updates
------------

06/03/2018

### 3.0 Release - Backwards incompatible

**New Features**

#### 1. The JSON loader has been removed in favour of a new `addRules` method.

```php
$dice->addRules([
'\PDO' => [
'shared' => true
],
'Framework\Router' => [
'constructParams' => ['Foo', 'Bar']
]
]);
```

The puropse of this addition is to make the JSON loader redundant. Loading of rules from a JSON file can easily be achieved with the code:

```php
$dice->addRules(json_decode(file_get_contents('rules.json')));
```

#### 2. Better JSON file support: constants and superglobals

In order to improve support for rules being defined in external JSON files, constants and superglobals can now be passed into objects created by Dice.

For example, passing the `$_SERVER` superglobal into a router instance and calling PDO's `setAttribute` with `PDO::ATTR_ERRMODE` and `PDO::ERRMODE_EXCEPTION` can be achieved like this in a JSON file:

_rules.json_

```json
{
"Router": {
"constructParams": [
{"Dice::GLOBAL": "_SERVER"}
]
},
"PDO": {
"shared": true,
"constructParams": [
"mysql:dbname=testdb;host=127.0.0.1",
"dbuser",
"dbpass"
],
"call": [
[
"setAttribute",
[
{"Dice::CONSTANT": "PDO::ATTR_ERRMODE"},
{"Dice::CONSTANT": "PDO::ERRMODE_EXCEPTION"}
]
]
]
}
}


```php
$dice->addRules(json_decode(file_get_contents('rules.json')));
```

**Backwards incompatible changes**

1. Dice 3.0 requires PHP 7.0 or above, PHP 5.6 is no longer supported.

2. Dice no longer supports `'instance'` keys to signify instances. For example:

```php
$dice->addRule('ClassName', [
'constructParams' => ['instance' => '$NamedPDOInstance']
]);
```

As noted in issue #125 this made it impossible to pass an array to a constructor if the array had a key `'instance'`. Instead, the new `\Dice\Dice::INSTANCE` constant should be used:

```php
$dice->addRule('ClassName', [
'constructParams' => [\Dice\Dice::INSTANCE => '$NamedPDOInstance']
]);
```
_to make the constant shorter to type out, you can `use \Dice\Dice;` and reference `Dice::INSTANCE`_

10/06/2016

** Backwards incompatible change **
Expand All @@ -99,7 +184,7 @@ $dice->addRule('$MyNamedInstance', $rule);

```

`$dice->create('$MyNamedInstance')` will now create a class following the rules applied to both `MyClass` and `$MyNamedInstance` so the instance will be shared.
`$dice->create('$MyNamedInstance')` will now create a class following the rules applied to both `MyClass` and `$MyNamedInstance` so the instance will be shared.

Previously only the rules applied to the named instance would be used.

Expand Down Expand Up @@ -127,7 +212,7 @@ $dice->addRule('$MyNamedInstance', $rule);


29/10/2014
* Based on [Issue #15](https://github.com/TomBZombie/Dice/issues/15), Dice will now only call closures if they are wrapped in \Dice\Instance. **PLEASE NOTE: THIS IS BACKWARDS INCOMPATIBLE **.
* Based on [Issue #15](https://github.com/TomBZombie/Dice/issues/15), Dice will now only call closures if they are wrapped in \Dice\Instance. **PLEASE NOTE: THIS IS BACKWARDS INCOMPATIBLE **.

Previously Dice ran closures that were passed as substitutions, constructParams and when calling methods:

Expand All @@ -146,9 +231,9 @@ $rule->constructParams[] = function() {
//'abc' will be providedas the first constructor parameter
return 'abc';
};
```
```

This behaviour has changed as it makes it impossible to provide a closure as a construct parameter or when calling a method because the closure was always called and executed.
This behaviour has changed as it makes it impossible to provide a closure as a construct parameter or when calling a method because the closure was always called and executed.

To overcome this, Dice will now only call a closures if they're wrapped in \Dice\Instance:

Expand All @@ -166,7 +251,7 @@ $rule->constructParams[] = ['instance' => function() { {
//'abc' will be providedas the first constructor parameter
return 'abc';
}]);
```
```



Expand Down Expand Up @@ -197,7 +282,7 @@ $rule->constructParams[] = ['instance' => function() { {
* Added a JSON loader + test case
* Added all test cases to a test suite
* Moved to PHP5.4 array syntax. A PHP5.3 compatible version is now available in the PHP5.3 branch.
* Fixed an issue where using named instances would trigger the autoloader with an invalid class name every time a class was created
* Fixed an issue where using named instances would trigger the autoloader with an invalid class name every time a class was created


28/02/2014
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "level-2/dice",
"description": "A minimalist Dependency injection container (DIC) for PHP. Please note: This branch is only compatible with PHP5.6. 5.5, 5.4 and 5.3 compatible version is available as a separate branch on github. ",
"description": "A minimalist Dependency injection container (DIC) for PHP. Please note: 3.0+ is only compatible with PHP 7.0. The 2.0 branch is compatbile with PHP 5.6.",
"license": "BSD-2-Clause",
"homepage": "http://r.je/dice.html",
"keywords": ["Dependency injection", "ioc", "Dependency injection container", "DI"],
Expand All @@ -11,7 +11,7 @@
}
],
"require": {
"php": ">=5.6.0"
"php": ">=7.0.0"
},
"autoload": {
"psr-4": {
Expand Down
Loading

0 comments on commit 9fc016a

Please sign in to comment.