Skip to content

Commit 9fc016a

Browse files
authored
Merge pull request #138 from Level-2/3.0-Dev-Rebase
3.0 dev rebase
2 parents c3a480e + 5ff15b5 commit 9fc016a

26 files changed

+568
-681
lines changed

Dice.php

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
<?php
22
/* @description Dice - A minimal Dependency Injection Container for PHP *
33
* @author Tom Butler tom@r.je *
4-
* @copyright 2012-2015 Tom Butler <tom@r.je> | https:// r.je/dice.html *
4+
* @copyright 2012-2018 Tom Butler <tom@r.je> | https:// r.je/dice.html *
55
* @license http:// www.opensource.org/licenses/bsd-license.php BSD License *
6-
* @version 2.0 */
6+
* @version 3.0 */
77
namespace Dice;
88
class Dice {
9+
const CONSTANT = 'Dice::CONSTANT';
10+
const GLOBAL = 'Dice::GLOBAL';
11+
const INSTANCE = 'Dice::INSTANCE';
912
/**
1013
* @var array $rules Rules which have been set using addRule()
1114
*/
@@ -26,17 +29,30 @@ class Dice {
2629
* @param string $name The name of the class to add the rule for
2730
* @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.
2831
*/
29-
public function addRule($name, array $rule) {
30-
if (isset($rule['instanceOf']) && (!array_key_exists('inherit', $rule) || $rule['inherit'] === true )) $rule = array_replace_recursive($this->getRule($rule['instanceOf']), $rule);
32+
public function addRule(string $name, array $rule) {
33+
if (isset($rule['instanceOf']) && (!array_key_exists('inherit', $rule) || $rule['inherit'] === true )) {
34+
$rule = array_replace_recursive($this->getRule($rule['instanceOf']), $rule);
35+
}
36+
//Allow substitutions rules to be defined with a leading a slash
37+
if (isset($rule['substitutions'])) foreach($rule['substitutions'] as $key => $value) $rule[ltrim($key, '\\')] = $value;
38+
3139
$this->rules[ltrim(strtolower($name), '\\')] = array_replace_recursive($this->getRule($name), $rule);
40+
}
41+
42+
/**
43+
* Add rules as array. Useful for JSON loading $dice->addRules(json_decode(file_get_contents('foo.json'));
44+
* @param array Rules in a single array [name => $rule] format
45+
*/
46+
public function addRules(array $rules) {
47+
foreach ($rules as $name => $rule) $this->addRule($name, $rule);
3248
}
3349

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

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

@@ -75,7 +91,7 @@ public function create($name, array $args = [], array $share = []) {
7591
* @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.
7692
* @return callable A closure
7793
*/
78-
private function getClosure($name, array $rule) {
94+
private function getClosure(string $name, array $rule) {
7995
// Reflect the class and constructor, this should only ever be done once per class and get cached
8096
$class = new \ReflectionClass(isset($rule['instanceOf']) ? $rule['instanceOf'] : $name);
8197
$constructor = $class->getConstructor();
@@ -114,8 +130,8 @@ private function getClosure($name, array $rule) {
114130
$object = $closure($args, $share);
115131

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

126142
/**
127-
* 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}
143+
* 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}
128144
* @param mixed $param Either a string or an array,
129-
* @param array $share Whether or not this class instance be shared, so that the same instance is passed around each time
145+
* @param array $share Array of instances from 'shareInstances', required for calls to `create`
130146
* @param bool $createFromString
131147
* @return mixed
132148
*/
133-
private function expand($param, array $share = [], $createFromString = false) {
134-
if (is_array($param) && isset($param['instance'])) {
135-
// Call or return the value sored under the key 'instance'
136-
// For ['instance' => ['className', 'methodName'] construct the instance before calling it
137-
$args = isset($param['params']) ? $this->expand($param['params']) : [];
138-
if (is_array($param['instance'])) $param['instance'][0] = $this->expand($param['instance'][0], $share, true);
139-
if (is_callable($param['instance'])) return call_user_func($param['instance'], ...$args);
140-
else return $this->create($param['instance'], array_merge($args, $share));
149+
private function expand($param, array $share = [], bool $createFromString = false) {
150+
if (is_array($param)) {
151+
//if a rule specifies Dice::INSTANCE, look up the relevant instance
152+
if (isset($param[self::INSTANCE])) {
153+
//Check for 'params' which allows parameters to be sent to the instance when it's created
154+
//Either as a callback method or to the constructor of the instance
155+
$args = isset($param['params']) ? $this->expand($param['params']) : [];
156+
157+
//Support Dice::INSTANCE by creating/fetching the specified instance
158+
if (is_callable($param[self::INSTANCE])) return call_user_func($param[self::INSTANCE], ...$args);
159+
else return $this->create($param[self::INSTANCE], array_merge($args, $share));
160+
}
161+
else if (isset($param[self::GLOBAL])) return $GLOBALS[$param[self::GLOBAL]];
162+
else if (isset($param[self::CONSTANT])) return constant($param[self::CONSTANT]);
163+
else foreach ($param as $name => $value) $param[$name] = $this->expand($value, $share);
141164
}
142-
// Recursively search for 'instance' keys in $param
143-
else if (is_array($param)) foreach ($param as $name => $value) $param[$name] = $this->expand($value, $share);
144-
// 'instance' wasn't found, return the value unchanged
165+
145166
return is_string($param) && $createFromString ? $this->create($param) : $param;
146167
}
147168

@@ -182,7 +203,6 @@ private function getParams(\ReflectionMethod $method, array $rule) {
182203
$parameters[] = $sub ? $this->expand($rule['substitutions'][$class], $share, true) : $this->create($class, [], $share);
183204
}
184205
catch (\InvalidArgumentException $e) {
185-
186206
}
187207
// For variadic parameters, provide remaining $args
188208
else if ($param->isVariadic()) $parameters = array_merge($parameters, $args);

Loader/Json.php

Lines changed: 0 additions & 33 deletions
This file was deleted.

Loader/Xml.php

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
<?php
2-
/* @description Dice - A minimal Dependency Injection Container for PHP
3-
* @author Tom Butler tom@r.je
4-
* @copyright 2012-2014 Tom Butler <tom@r.je>
5-
* @link http://r.je/dice.html
6-
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
7-
* @version 2.0
8-
*/
1+
<?php
2+
/* @description Dice - A minimal Dependency Injection Container for PHP *
3+
* @author Tom Butler tom@r.je *
4+
* @copyright 2012-2018 Tom Butler <tom@r.je> | https:// r.je/dice.html *
5+
* @license http:// www.opensource.org/licenses/bsd-license.php BSD License *
6+
* @version 3.0 */
97
namespace Dice\Loader;
108
class Xml {
119
private function getComponent(\SimpleXmlElement $element, $forceInstance = false) {
12-
if ($forceInstance) return ['instance' => (string) $element];
13-
else if ($element->instance) return ['instance' => (string) $element->instance];
10+
if ($forceInstance) return [\Dice\Dice::INSTANCE => (string) $element];
11+
else if ($element->instance) return [\Dice\Dice::INSTANCE => (string) $element->instance];
1412
else return (string) $element;
1513
}
1614

README.md

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Simple example:
2727
<?php
2828
class A {
2929
public $b;
30-
30+
3131
public function __construct(B $b) {
3232
$this->b = $b;
3333
}
@@ -63,7 +63,7 @@ Dice is compatible with PHP5.4 and up, there are archived versions of Dice which
6363
Performance
6464
-----------
6565

66-
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.
66+
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.
6767

6868
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.
6969

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

80+
06/03/2018
81+
82+
### 3.0 Release - Backwards incompatible
83+
84+
**New Features**
85+
86+
#### 1. The JSON loader has been removed in favour of a new `addRules` method.
87+
88+
```php
89+
$dice->addRules([
90+
'\PDO' => [
91+
'shared' => true
92+
],
93+
'Framework\Router' => [
94+
'constructParams' => ['Foo', 'Bar']
95+
]
96+
]);
97+
```
98+
99+
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:
100+
101+
```php
102+
$dice->addRules(json_decode(file_get_contents('rules.json')));
103+
```
104+
105+
#### 2. Better JSON file support: constants and superglobals
106+
107+
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.
108+
109+
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:
110+
111+
_rules.json_
112+
113+
```json
114+
{
115+
"Router": {
116+
"constructParams": [
117+
{"Dice::GLOBAL": "_SERVER"}
118+
]
119+
},
120+
"PDO": {
121+
"shared": true,
122+
"constructParams": [
123+
"mysql:dbname=testdb;host=127.0.0.1",
124+
"dbuser",
125+
"dbpass"
126+
],
127+
"call": [
128+
[
129+
"setAttribute",
130+
[
131+
{"Dice::CONSTANT": "PDO::ATTR_ERRMODE"},
132+
{"Dice::CONSTANT": "PDO::ERRMODE_EXCEPTION"}
133+
]
134+
]
135+
]
136+
}
137+
}
138+
139+
140+
```php
141+
$dice->addRules(json_decode(file_get_contents('rules.json')));
142+
```
143+
144+
**Backwards incompatible changes**
145+
146+
1. Dice 3.0 requires PHP 7.0 or above, PHP 5.6 is no longer supported.
147+
148+
2. Dice no longer supports `'instance'` keys to signify instances. For example:
149+
150+
```php
151+
$dice->addRule('ClassName', [
152+
'constructParams' => ['instance' => '$NamedPDOInstance']
153+
]);
154+
```
155+
156+
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:
157+
158+
```php
159+
$dice->addRule('ClassName', [
160+
'constructParams' => [\Dice\Dice::INSTANCE => '$NamedPDOInstance']
161+
]);
162+
```
163+
_to make the constant shorter to type out, you can `use \Dice\Dice;` and reference `Dice::INSTANCE`_
164+
80165
10/06/2016
81166

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

100185
```
101186

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

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

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

128213

129214
29/10/2014
130-
* 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 **.
215+
* 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 **.
131216

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

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

151-
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.
236+
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.
152237

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

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

171256

172257

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

202287

203288
28/02/2014

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "level-2/dice",
3-
"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. ",
3+
"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.",
44
"license": "BSD-2-Clause",
55
"homepage": "http://r.je/dice.html",
66
"keywords": ["Dependency injection", "ioc", "Dependency injection container", "DI"],
@@ -11,7 +11,7 @@
1111
}
1212
],
1313
"require": {
14-
"php": ">=5.6.0"
14+
"php": ">=7.0.0"
1515
},
1616
"autoload": {
1717
"psr-4": {

0 commit comments

Comments
 (0)