├── .gitignore ├── Dice.php ├── Extra └── RuleValidator.php ├── Loader └── Xml.php ├── README.md ├── composer.json ├── phpunit.xml └── tests ├── BasicTest.php ├── CallTest.php ├── ChainTest.php ├── ConstructParamsTest.php ├── CreateArgsTest.php ├── DiceTest.php ├── NamedInstancesTest.php ├── NamespaceTest.php ├── ShareInstancesTest.php ├── SubstitutionsTest.php ├── TestData ├── Basic.php ├── Call.php ├── ConstructParams.php ├── CreateArgs.php ├── NamedInstances.php ├── Namespace.php └── ShareInstances.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | -------------------------------------------------------------------------------- /Dice.php: -------------------------------------------------------------------------------- 1 | | https://r.je/dice 5 | * @license http://www.opensource.org/licenses/bsd-license.php BSD License 6 | * @version 4.0 */ 7 | namespace Dice; 8 | class Dice { 9 | const CONSTANT = 'Dice::CONSTANT'; 10 | const GLOBAL = 'Dice::GLOBAL'; 11 | const INSTANCE = 'Dice::INSTANCE'; 12 | const CHAIN_CALL = 'Dice::CHAIN_CALL'; 13 | const SELF = 'Dice::SELF'; 14 | /** 15 | * @var array $rules Rules which have been set using addRule() 16 | */ 17 | private $rules = []; 18 | 19 | /** 20 | * @var array $cache A cache of closures based on class name so each class is only reflected once 21 | */ 22 | private $cache = []; 23 | 24 | /** 25 | * @var array $instances Stores any instances marked as 'shared' so create() can return the same instance 26 | */ 27 | private $instances = []; 28 | 29 | /** 30 | * Add a rule $rule to the class $name 31 | * @param string $name The name of the class to add the rule for 32 | * @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. 33 | */ 34 | public function addRule(string $name, array $rule): self { 35 | $dice = clone $this; 36 | $this->addRuleTo($dice, $name, $rule); 37 | return $dice; 38 | } 39 | 40 | /** 41 | * Add rules as array. Useful for JSON loading $dice->addRules(json_decode(file_get_contents('foo.json')); 42 | * @param array Rules in a single array [name => $rule] format 43 | */ 44 | public function addRules($rules): self { 45 | if (is_string($rules)) $rules = json_decode(file_get_contents($rules), true); 46 | $dice = clone $this; 47 | foreach ($rules as $name => $rule) $this->addRuleTo($dice,$name, $rule); 48 | return $dice; 49 | } 50 | 51 | private function addRuleTo(Dice $dice, string $name, array $rule) { 52 | if (isset($rule['instanceOf']) && (!array_key_exists('inherit', $rule) || $rule['inherit'] === true )) 53 | $rule = array_replace_recursive($dice->getRule($rule['instanceOf']), $rule); 54 | //Allow substitutions rules to be defined with a leading a slash 55 | if (isset($rule['substitutions'])) foreach($rule['substitutions'] as $key => $value) $rule['substitutions'][ltrim($key, '\\')] = $value; 56 | //Clear any existing instance or cache for this class 57 | unset($dice->instances[$name], $dice->cache[$name]); 58 | $dice->rules[ltrim(strtolower($name), '\\')] = array_replace_recursive($dice->getRule($name), $rule); 59 | } 60 | 61 | /** 62 | * Returns the rule that will be applied to the class $name when calling create() 63 | * @param string name The name of the class to get the rules for 64 | * @return array The rules for the specified class 65 | */ 66 | public function getRule(string $name): array { 67 | $lcName = strtolower(ltrim($name, '\\')); 68 | if (isset($this->rules[$lcName])) return $this->rules[$lcName]; 69 | 70 | foreach ($this->rules as $key => $rule) { // Find a rule which matches the class described in $name where: 71 | if (empty($rule['instanceOf']) // It's not a named instance, the rule is applied to a class name 72 | && $key !== '*' // It's not the default rule 73 | && is_subclass_of($name, $key) // The rule is applied to a parent class 74 | && (!array_key_exists('inherit', $rule) || $rule['inherit'] === true )) // And that rule should be inherited to subclasses 75 | return $rule; 76 | } 77 | // No rule has matched, return the default rule if it's set 78 | return isset($this->rules['*']) ? $this->rules['*'] : []; 79 | } 80 | 81 | /** 82 | * Returns a fully constructed object based on $name using $args and $share as constructor arguments if supplied 83 | * @param string name The name of the class to instantiate 84 | * @param array $args An array with any additional arguments to be passed into the constructor upon instantiation 85 | * @param array $share a list of defined in shareInstances for objects higher up the object graph, should only be used internally 86 | * @return object A fully constructed object based on the specified input arguments 87 | */ 88 | public function create(string $name, array $args = [], array $share = []) { 89 | // Is there a shared instance set? Return it. Better here than a closure for this, calling a closure is slower. 90 | if (!empty($this->instances[$name])) return $this->instances[$name]; 91 | 92 | // Create a closure for creating the object if there isn't one already 93 | if (empty($this->cache[$name])) $this->cache[$name] = $this->getClosure(ltrim($name, '\\'), $this->getRule($name)); 94 | 95 | // Call the cached closure which will return a fully constructed object of type $name 96 | return $this->cache[$name]($args, $share); 97 | } 98 | 99 | /** 100 | * Returns a closure for creating object $name based on $rule, caching the reflection object for later use 101 | * @param string $name the Name of the class to get the closure for 102 | * @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. 103 | * @return callable A closure 104 | */ 105 | private function getClosure(string $name, array $rule) { 106 | // Reflect the class and constructor, this should only ever be done once per class and get cached 107 | $class = new \ReflectionClass(isset($rule['instanceOf']) ? $rule['instanceOf'] : $name); 108 | $constructor = $class->getConstructor(); 109 | 110 | // Create parameter generating function in order to cache reflection on the parameters. This way $reflect->getParameters() only ever gets called once 111 | $params = $constructor ? $this->getParams($constructor, $rule) : null; 112 | //PHP throws a fatal error rather than an exception when trying to instantiate an interface, detect it and throw an exception instead 113 | if ($class->isInterface()) $closure = function() { 114 | throw new \InvalidArgumentException('Cannot instantiate interface'); 115 | }; 116 | // Get a closure based on the type of object being created: Shared, normal or constructorless 117 | else if ($params) $closure = function (array $args, array $share) use ($class, $params) { 118 | // This class has depenencies, call the $params closure to generate them based on $args and $share 119 | return new $class->name(...$params($args, $share)); 120 | }; 121 | else $closure = function () use ($class) { // No constructor arguments, just instantiate the class 122 | return new $class->name; 123 | }; 124 | 125 | if (!empty($rule['shared'])) $closure = function (array $args, array $share) use ($class, $name, $constructor, $params, $closure) { 126 | //Internal classes may not be able to be constructed without calling the constructor and will not suffer from #7, construct them normally. 127 | if ($class->isInternal()) $this->instances[$class->name] = $this->instances['\\' . $class->name] = $closure($args, $share); 128 | else { 129 | //Otherwise, create the class without calling the constructor (and write to \$name and $name, see issue #68) 130 | $this->instances[$name] = $this->instances['\\' . $name] = $class->newInstanceWithoutConstructor(); 131 | // Now call this constructor after constructing all the dependencies. This avoids problems with cyclic references (issue #7) 132 | if ($constructor) $constructor->invokeArgs($this->instances[$name], $params($args, $share)); 133 | } 134 | return $this->instances[$name]; 135 | }; 136 | // If there are shared instances, create them and merge them with shared instances higher up the object graph 137 | if (isset($rule['shareInstances'])) $closure = function(array $args, array $share) use ($closure, $rule) { 138 | foreach($rule['shareInstances'] as $instance) $share[] = $this->create($instance, [], $share); 139 | return $closure($args, $share); 140 | }; 141 | // When $rule['call'] is set, wrap the closure in another closure which will call the required methods after constructing the object 142 | // By putting this in a closure, the loop is never executed unless call is actually set 143 | return isset($rule['call']) ? function (array $args, array $share) use ($closure, $class, $rule, $name) { 144 | // Construct the object using the original closure 145 | $object = $closure($args, $share); 146 | 147 | foreach ($rule['call'] as $call) { 148 | // Generate the method arguments using getParams() and call the returned closure 149 | $params = $this->getParams($class->getMethod($call[0]), ['shareInstances' => isset($rule['shareInstances']) ? $rule['shareInstances'] : [] ])(($this->expand(isset($call[1]) ? $call[1] : [])), $share); 150 | $return = $object->{$call[0]}(...$params); 151 | if (isset($call[2])) { 152 | if ($call[2] === self::CHAIN_CALL) { 153 | if (!empty($rule['shared'])) $this->instances[$name] = $return; 154 | if (is_object($return)) $class = new \ReflectionClass(get_class($return)); 155 | $object = $return; 156 | } 157 | else if (is_callable($call[2])) call_user_func($call[2], $return); 158 | } 159 | } 160 | return $object; 161 | } : $closure; 162 | } 163 | 164 | /** 165 | * 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} 166 | * @param mixed $param Either a string or an array, 167 | * @param array $share Array of instances from 'shareInstances', required for calls to `create` 168 | * @param bool $createFromString 169 | * @return mixed 170 | */ 171 | private function expand($param, array $share = [], bool $createFromString = false) { 172 | if (is_array($param)) { 173 | //if a rule specifies Dice::INSTANCE, look up the relevant instance 174 | if (isset($param[self::INSTANCE])) { 175 | if ($param[self::INSTANCE] === self::SELF) return $this; 176 | //Check for 'params' which allows parameters to be sent to the instance when it's created 177 | //Either as a callback method or to the constructor of the instance 178 | $args = isset($param['params']) ? $this->expand($param['params']) : []; 179 | 180 | //Support Dice::INSTANCE by creating/fetching the specified instance 181 | if (is_array($param[self::INSTANCE])) $param[self::INSTANCE][0] = $this->expand($param[self::INSTANCE][0], $share, true); 182 | if (is_callable($param[self::INSTANCE])) return call_user_func($param[self::INSTANCE], ...$args); 183 | else return $this->create($param[self::INSTANCE], array_merge($args, $share)); 184 | } 185 | else if (isset($param[self::GLOBAL])) return $GLOBALS[$param[self::GLOBAL]]; 186 | else if (isset($param[self::CONSTANT])) return constant($param[self::CONSTANT]); 187 | else foreach ($param as $name => $value) $param[$name] = $this->expand($value, $share); 188 | } 189 | 190 | return is_string($param) && $createFromString ? $this->create($param) : $param; 191 | } 192 | /** 193 | * Looks through the array $search for any object which can be used to fulfil $param 194 | The original array $search is modifed so must be passed by reference. 195 | 196 | */ 197 | private function matchParam(\ReflectionParameter $param, $class, array &$search) { 198 | foreach ($search as $i => $arg) { 199 | if ($class && ($arg instanceof $class || ($arg === null && $param->allowsNull()))) { 200 | // The argument matched, return it and remove it from $search so it won't wrongly match another parameter 201 | return array_splice($search, $i, 1)[0]; 202 | } 203 | } 204 | return false; 205 | } 206 | /** 207 | * Returns a closure that generates arguments for $method based on $rule and any $args passed into the closure 208 | * @param object $method An instance of ReflectionMethod (see: {@link http:// php.net/manual/en/class.reflectionmethod.php}) 209 | * @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. 210 | * @return callable A closure that uses the cached information to generate the arguments for the method 211 | */ 212 | private function getParams(\ReflectionMethod $method, array $rule) { 213 | // Cache some information about the parameter in $paramInfo so (slow) reflection isn't needed every time 214 | $paramInfo = []; 215 | foreach ($method->getParameters() as $param) { 216 | $type = $param->getType(); 217 | 218 | $class = $type instanceof \ReflectionNamedType && !$type->isBuiltIn() ? $type->getName() : null; 219 | 220 | $paramInfo[] = [$class, $param, isset($rule['substitutions']) && array_key_exists($class, $rule['substitutions'])]; 221 | } 222 | 223 | // Return a closure that uses the cached information to generate the arguments for the method 224 | return function (array $args, array $share = []) use ($paramInfo, $rule) { 225 | // If the rule has construtParams set, construct any classes reference and use them as $args 226 | if (isset($rule['constructParams'])) $args = array_merge($args, $this->expand($rule['constructParams'], $share)); 227 | 228 | // Array of matched parameters 229 | $parameters = []; 230 | 231 | // Fnd a value for each method argument 232 | foreach ($paramInfo as list($class, $param, $sub)) { 233 | // Loop through $args and see whether or not each value can match the current parameter based on type hint 234 | if ($args && ($match = $this->matchParam($param, $class, $args)) !== false) { 235 | $parameters[] = $match; 236 | } 237 | // Do the same with $share 238 | else if (($copy = $share) && ($match = $this->matchParam($param, $class, $copy)) !== false) { 239 | $parameters[] = $match; 240 | } 241 | // When nothing from $args or $share matches but a class is type hinted, create an instance to use, using a substitution if set 242 | else if ($class) try { 243 | if ($sub) { 244 | $parameters[] = $this->expand($rule['substitutions'][$class], $share, true); 245 | } 246 | else { 247 | $parameters[] = !$param->allowsNull() ? $this->create($class, [], $share) : null; 248 | } 249 | } 250 | catch (\InvalidArgumentException $e) { 251 | } 252 | // Support PHP 7 scalar type hinting, is_a('string', 'foo') doesn't work so this is a hacky AF workaround: call_user_func('is_' . $type, '') 253 | 254 | //Find a match in $args for scalar types 255 | else if ($args && $param->getType()) { 256 | for ($i = 0; $i < count($args); $i++) { 257 | if (call_user_func('is_' . $param->getType()->getName(), $args[$i])) { 258 | $parameters[] = array_splice($args, $i, 1)[0]; 259 | break; 260 | } 261 | } 262 | } 263 | else if ($args) { 264 | $parameters[] = $this->expand(array_shift($args)); 265 | } 266 | // For variadic parameters, provide remaining $args 267 | else if ($param->isVariadic()) { 268 | $parameters = array_merge($parameters, $args); 269 | } 270 | // There's no type hint and nothing left in $args, provide the default value or null 271 | else { 272 | $parameters[] = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null; 273 | } 274 | } 275 | return $parameters; 276 | }; 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /Extra/RuleValidator.php: -------------------------------------------------------------------------------- 1 | dice = $dice; 8 | } 9 | 10 | public function addRule($name, array $rule) { 11 | $this->checkValidKeys($rule); 12 | $this->checkBoolean($rule, 'inherit'); 13 | $this->checkBoolean($rule, 'shared'); 14 | $this->checkNumericArray($rule, 'constructParams'); 15 | $this->checkNumericArray($rule, 'shareInstances'); 16 | $this->checkNumericArray($rule, 'call'); 17 | $this->dice->addRule($name, $rule); 18 | } 19 | 20 | private function checkValidKeys($rule) { 21 | $validKeys = ['call', 'shared', 'substitutions', 'instanceOf', 'inherit', 'shareInstances', 'constructParams']; 22 | foreach ($rule as $name => $value) { 23 | if (!in_array($name, $validKeys)) throw new \InvalidArgumentException('Invalid rule option: '. $name); 24 | } 25 | } 26 | 27 | public function create($name, array $args = [], array $share = []) { 28 | return $this->dice->create($name, $args, $share); 29 | } 30 | 31 | public function checkBoolean($rule, $key) { 32 | if (!isset($rule[$key])) return; 33 | 34 | if (!is_bool($rule[$key])) throw new \InvalidArgumentException('Rule option ' . $key . ' must be true or false'); 35 | } 36 | 37 | public function checkNumericArray($rule, $key) { 38 | if (!isset($rule[$key])) return; 39 | 40 | if (count(array_filter(array_keys($rule[$key]), 'is_string')) > 0) throw new \InvalidArgumentException('Rule option ' . $key . ' must be a seqential array not an associative array'); 41 | 42 | } 43 | 44 | public function checkAssocArray($rule, $key) { 45 | if (!isset($rule[$key])) return; 46 | 47 | if (count(array_filter(array_keys($rule[$key]), 'is_string')) === 0) throw new \InvalidArgumentException('Rule option ' . $key . ' must be a an associative array'); 48 | 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /Loader/Xml.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | namespace Dice\Loader; 8 | class Xml { 9 | private function getComponent(\SimpleXmlElement $element, $forceInstance = false) { 10 | if ($forceInstance) return [\Dice\Dice::INSTANCE => (string) $element]; 11 | else if ($element->instance) return [\Dice\Dice::INSTANCE => (string) $element->instance]; 12 | else return (string) $element; 13 | } 14 | 15 | private function loadV1(\SimpleXmlElement $xml, \Dice\Dice $dice) { 16 | $rules = []; 17 | 18 | foreach ($xml as $key => $value) { 19 | $rule = $dice->getRule((string) $value->name); 20 | 21 | if (isset($value->shared)) $rule['shared'] = ((string) $value->shared === 'true'); 22 | 23 | if (isset($value->inherit)) $rule['inherit'] = ($value->inherit == 'false') ? false : true; 24 | if ($value->call) { 25 | foreach ($value->call as $name => $call) { 26 | $callArgs = []; 27 | if ($call->params) foreach ($call->params->children() as $key => $param) $callArgs[] = $this->getComponent($param); 28 | $rule['call'][] = [(string) $call->method, $callArgs]; 29 | } 30 | } 31 | if ($value->instanceOf) $rule['instanceOf'] = (string) $value->instanceOf; 32 | if ($value->newInstances) foreach ($value->newInstances as $ni) $rule['newInstances'][] = (string) $ni; 33 | if ($value->substitutions) foreach ($value->substitutions as $use) $rule['substitutions'][(string) $use->as] = $this->getComponent($use->use, true); 34 | if ($value->constructParams) foreach ($value->constructParams->children() as $child) $rule['constructParams'][] = $this->getComponent($child); 35 | if ($value->shareInstances) foreach ($value->shareInstances as $share) $rule['shareInstances'][] = $this->getComponent($share); 36 | $rules[$value['name']] = $rule; 37 | $dice->addRule((string) $value->name, $rule); 38 | } 39 | return $rules; 40 | } 41 | 42 | private function loadV2(\SimpleXmlElement $xml, \Dice\Dice $dice) { 43 | $rules = []; 44 | 45 | foreach ($xml as $key => $value) { 46 | $rule = $dice->getRule((string) $value->name); 47 | 48 | if ($value->call) { 49 | foreach ($value->call as $name => $call) { 50 | $callArgs = []; 51 | foreach ($call->children() as $key => $param) $callArgs[] = $this->getComponent($param); 52 | $rule['call'][] = [(string) $call['method'], $callArgs]; 53 | } 54 | } 55 | if (isset($value['inherit'])) $rule['inherit'] = ($value['inherit'] == 'false') ? false : true; 56 | if ($value['instanceOf']) $rule['instanceOf'] = (string) $value['instanceOf']; 57 | if (isset($value['shared'])) $rule['shared'] = ((string) $value['shared'] === 'true'); 58 | if ($value->constructParams) foreach ($value->constructParams->children() as $child) $rule['constructParams'][] = $this->getComponent($child); 59 | if ($value->substitute) foreach ($value->substitute as $use) $rule['substitutions'][(string) $use['as']] = $this->getComponent($use['use'], true); 60 | if ($value->shareInstances) foreach ($value->shareInstances->children() as $share) $rule['shareInstances'][] = $this->getComponent($share); 61 | $rules[$value['name']] = $rule; 62 | $dice->addRule((string) $value['name'], $rule); 63 | } 64 | return $rules; 65 | } 66 | 67 | public function load($xml, \Dice\Dice $dice = null, $displayWarning = true) { 68 | 69 | if ($displayWarning) { 70 | trigger_error('Deprecated: The XML loader is being removed in the next version of Dice please use $xmlLoader->convert(\'' . $xml . '\', \'path/to/rules.json\'); to convert the rules to JSON format', E_USER_WARNING); 71 | } 72 | 73 | if ($dice === null) $dice = new \Dice\Dice; 74 | if (!($xml instanceof \SimpleXmlElement)) $xml = simplexml_load_file($xml); 75 | $ns = $xml->getNamespaces(); 76 | $nsName = (isset($ns[''])) ? $ns[''] : ''; 77 | 78 | if ($nsName == 'https://r.je/dice/2.0') return $this->loadV2($xml, $dice); 79 | else return $this->loadV1($xml, $dice); 80 | } 81 | 82 | public function convert($xml, $outputJson) { 83 | $rules = $this->load($xml); 84 | 85 | file_put_contents($outputJson, json_encode($rules, JSON_PRETTY_PRINT)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [Dice PHP Dependency Injection Container](https://r.je/dice.html) 2 | ====================================== 3 | 4 | Dice is a minimalist Dependency Injection Container for PHP with a focus on being lightweight and fast as well as requiring as little configuration as possible. 5 | 6 | 7 | Project Goals 8 | ------------- 9 | 10 | 1) To be lightweight and not a huge library with dozens of files (Dice is a single 100 line class) yet support all features (and more) offered by much more complex containers 11 | 12 | 2) To "just work". Basic functionality should work with zero configuration 13 | 14 | 3) Where configuration is required, it should be as minimal and reusable as possible as well as easy to use. 15 | 16 | 4) Speed! (See [the section on performance](#performance)) 17 | 18 | 19 | Installation 20 | ------------ 21 | 22 | Just include the lightweight `Dice.php` in your project and it's usable without any further configuration: 23 | 24 | Simple example: 25 | 26 | ```php 27 | b = $b; 33 | } 34 | } 35 | 36 | class B { 37 | 38 | } 39 | 40 | require_once 'Dice.php'; 41 | $dice = new \Dice\Dice; 42 | 43 | $a = $dice->create('A'); 44 | 45 | var_dump($a->b); //B object 46 | 47 | ?> 48 | ``` 49 | 50 | 51 | Full Documentation 52 | ------------------ 53 | 54 | For complete documentation please see the [Dice PHP Dependency Injection container home page](https://r.je/dice.html) 55 | 56 | 57 | PHP version compatibility 58 | ------------------------- 59 | 60 | Dice is compatible with PHP 7.0 and up, there are archived versions of Dice which support PHP 5.6 however this is no longer maintanied. 61 | 62 | 63 | Performance 64 | ----------- 65 | 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. 67 | 68 | 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. 69 | 70 | 71 | Credits 72 | ------------ 73 | 74 | Originally developed by Tom Butler (@TomBZombie), with many thanks to daniel-meister (@daniel-meister), Garrett W. (@garrettw), maxwilms (@maxwilms) for bug fixes, suggestions and improvements. 75 | 76 | 77 | Updates 78 | ------------ 79 | 80 | ### 15/11/2018 4.0 Release - Backwards incompatible 81 | 82 | Dice is now immutable and has better support for other immutable objects. 83 | 84 | **New Features** 85 | 86 | #### 1. Dice is Immutable 87 | 88 | This avoids [issues surrounding mutability](https://www.yegor256.com/2014/06/09/objects-should-be-immutable.html) where a Dice instance is passed around the application and reconfigured. The only difference is that `addRules` and `addRule` return a new Dice instance with the updated rules rather than changing the state of the existing instance. 89 | 90 | ```php 91 | 92 | // Pre-4.0 code: 93 | $dice->addRule('PDO', ['shared' => true]); 94 | 95 | $db = $dice->create('PDO'); 96 | 97 | // 4.0 code: 98 | $dice = $dice->addRule('PDO', ['shared' => true]); 99 | 100 | $db = $dice->create('PDO'); 101 | ``` 102 | 103 | From a practical perspective in most cases just put `$dice = ` in front of any `$dice->addRule()` call and it will work as before. 104 | 105 | #### 2. Support for Object Method Chaining 106 | 107 | One feature some immutable objects have is they offer object chaining. 108 | 109 | Consider the following Object: 110 | 111 | ```php 112 | 113 | $httpRequest = new HTTPRequest(); 114 | $httpRequest = $httpRequest->url('http://example.org')->method('POST')->postdata('foo=bar'); 115 | ``` 116 | 117 | It was not possible for Dice to consturuct the configured object in previous versions. As of 4.0 Dice supports chaining method call using the `call` rule and the `Dice::CHAIN_CALL` constant: 118 | 119 | ```php 120 | $dice = $dice->addRule('HTTPRequest', 121 | ['call' => [ 122 | ['url', ['http://example.org'], Dice::CHAIN_CALL], 123 | ['method', ['POST'], Dice::CHAIN_CALL ], 124 | ['postdata', ['foo=bar'], Dice::CHAIN_CALL] 125 | ] 126 | ] 127 | ); 128 | ``` 129 | 130 | Dice will replace the HTTPRequest object with the result of the chained call. This is also useful for factories: 131 | 132 | 133 | ```php 134 | $dice = $dice->addRule('MyDatabase', 135 | [ 136 | 'instanceOf' => 'DatabaseFactory', 137 | 'call' => [ 138 | ['get', ['Database'], Dice::CHAIN_CALL] 139 | ] 140 | ] 141 | ); 142 | 143 | $database = $dice->create('MyDatabase'); 144 | //Equivalent of: 145 | 146 | $factory = new DatabaseFactory(); 147 | $database = $factory->get('Database'); 148 | ``` 149 | 150 | 151 | ### 06/03/2018 3.0 Release - Backwards incompatible 152 | 153 | **New Features** 154 | 155 | #### 1. The JSON loader has been removed in favour of a new `addRules` method. 156 | 157 | ```php 158 | $dice->addRules([ 159 | '\PDO' => [ 160 | 'shared' => true 161 | ], 162 | 'Framework\Router' => [ 163 | 'constructParams' => ['Foo', 'Bar'] 164 | ] 165 | ]); 166 | ``` 167 | 168 | The purpose of this addition is to make the JSON loader redundant. Loading of rules from a JSON file can easily be achieved with the code: 169 | 170 | ```php 171 | $dice->addRules(json_decode(file_get_contents('rules.json'))); 172 | ``` 173 | 174 | #### 2. Better JSON file support: constants and superglobals 175 | 176 | 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. 177 | 178 | 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: 179 | 180 | _rules.json_ 181 | 182 | ```json 183 | { 184 | "Router": { 185 | "constructParams": [ 186 | {"Dice::GLOBAL": "_SERVER"} 187 | ] 188 | }, 189 | "PDO": { 190 | "shared": true, 191 | "constructParams": [ 192 | "mysql:dbname=testdb;host=127.0.0.1", 193 | "dbuser", 194 | "dbpass" 195 | ], 196 | "call": [ 197 | [ 198 | "setAttribute", 199 | [ 200 | {"Dice::CONSTANT": "PDO::ATTR_ERRMODE"}, 201 | {"Dice::CONSTANT": "PDO::ERRMODE_EXCEPTION"} 202 | ] 203 | ] 204 | ] 205 | } 206 | } 207 | ``` 208 | 209 | ```php 210 | $dice->addRules(json_decode(file_get_contents('rules.json'))); 211 | ``` 212 | 213 | **Backwards incompatible changes** 214 | 215 | 1. Dice 3.0 requires PHP 7.0 or above, PHP 5.6 is no longer supported. 216 | 217 | 2. Dice no longer supports `'instance'` keys to signify instances. For example: 218 | 219 | ```php 220 | $dice->addRule('ClassName', [ 221 | 'constructParams' => ['instance' => '$NamedPDOInstance'] 222 | ]); 223 | ``` 224 | 225 | 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: 226 | 227 | ```php 228 | $dice->addRule('ClassName', [ 229 | 'constructParams' => [\Dice\Dice::INSTANCE => '$NamedPDOInstance'] 230 | ]); 231 | ``` 232 | _to make the constant shorter to type out, you can `use \Dice\Dice;` and reference `Dice::INSTANCE`_ 233 | 234 | 10/06/2016 235 | 236 | ** Backwards incompatible change ** 237 | 238 | Based on [Issue 110](https://github.com/Level-2/Dice/pull/110) named instances using `instanceOf` will now inherit the rules applied to the class they are instances of: 239 | 240 | ```php 241 | 242 | $rule = []; 243 | $rule['shared'] = true; 244 | 245 | $dice->addRule('MyClass', $rule); 246 | 247 | $rule = []; 248 | $rule['instanceOf'] = 'MyClass'; 249 | $rule['constructParams'] = ['Foo', 'Bar']; 250 | 251 | $dice->addRule('$MyNamedInstance', $rule); 252 | 253 | 254 | ``` 255 | 256 | `$dice->create('$MyNamedInstance')` will now create a class following the rules applied to both `MyClass` and `$MyNamedInstance` so the instance will be shared. 257 | 258 | Previously only the rules applied to the named instance would be used. 259 | 260 | To restore the old behaviour, set `inherit` to `false` on the named instance: 261 | 262 | ```php 263 | $rule = []; 264 | $rule['shared'] = true; 265 | 266 | $dice->addRule('MyClass', $rule); 267 | 268 | $rule = []; 269 | $rule['instanceOf'] = 'MyClass'; 270 | $rule['constructParams'] = ['Foo', 'Bar']; 271 | 272 | 273 | //Prevent the named instance inheriting rules from the class named in `instanceOf`: 274 | $rule['inherit'] = false; 275 | 276 | $dice->addRule('$MyNamedInstance', $rule); 277 | 278 | ``` 279 | 280 | 281 | 282 | 283 | 29/10/2014 284 | * 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 **. 285 | 286 | Previously Dice ran closures that were passed as substitutions, constructParams and when calling methods: 287 | 288 | ```php 289 | 290 | $rule->substitutions['A'] = function() { 291 | return new A; 292 | }; 293 | 294 | $rule->call[] = ['someMethod', function() { 295 | // '2' will be provided as the first argument when someMethod is called 296 | return 2; 297 | }]; 298 | 299 | $rule->constructParams[] = function() { 300 | //'abc' will be providedas the first constructor parameter 301 | return 'abc'; 302 | }; 303 | ``` 304 | 305 | 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. 306 | 307 | To overcome this, Dice will now only call a closures if they're wrapped in \Dice\Instance: 308 | 309 | ```php 310 | $rule->substitutions['A'] = ['instance' => function() { 311 | return new A; 312 | }]; 313 | 314 | $rule->call[] = ['someMethod', ['instance' => function() { 315 | // '2' will be provided as the first argument when someMethod is called 316 | return 2; 317 | }]]); 318 | 319 | $rule->constructParams[] = ['instance' => function() { { 320 | //'abc' will be providedas the first constructor parameter 321 | return 'abc'; 322 | }]); 323 | ``` 324 | 325 | 326 | 327 | 328 | 329 | 04/09/2014 330 | * Pushed PHP5.6 branch live. This is slightly more efficient using PHP5.6 features. For PHP5.4-PHP5.5 please see the relevant branch. This version will be maintained until PHP5.6 is more widespread. 331 | 332 | 333 | 26/08/2014 334 | * Added PHP5.6 branch. Tidied up code by using PHP5.6 features. This will be moved to master when PHP5.6 is released 335 | 336 | 28/06/2014 337 | * Greatly improved efficienty. Dice is now the fastest Dependency Injection Container for PHP! 338 | 339 | 06/06/2014 340 | * Added support for cyclic references ( https://github.com/TomBZombie/Dice/issues/7 ). Please note this is poor design but this fix will stop the infinite loop this design creates. 341 | 342 | 27/03/2014 343 | * Removed assign() method as this duplicated functionality available using $rule->shared 344 | * Removed $callback argument in $dice->create() as the only real use for this feature can be better achieved using $rule->shareInstances 345 | * Tidied up code, removing unused/undocumented features. Dice is now even more lightweight and faster. 346 | * Fixed a bug where when using $rule->call it would use the substitution rules from the constructor on each method called 347 | * Updated [Dice documentation](https://r.je/dice.html) to use shorthand array syntax 348 | 349 | 01/03/2014 350 | * Added test cases for the Xml Loader and Loader Callback classes 351 | * Added a JSON loader + test case 352 | * Added all test cases to a test suite 353 | * Moved to PHP5.4 array syntax. A PHP5.3 compatible version is now available in the PHP5.3 branch. 354 | * Fixed an issue where using named instances would trigger the autoloader with an invalid class name every time a class was created 355 | 356 | 357 | 28/02/2014 358 | * Added basic namespace support. Documentation update will follow shortly. Also moved the XML loader into its own file, you'll need to include it separately if you're using it. 359 | * Please note: CHANGES ARE NOT BACKWARDS COMPATIBLE. However they are easily fixed by doing the following find/replaces: 360 | 361 | ```php 362 | new Dice => new \Dice\Dice 363 | new DiceInstance => new \Dice\Instance 364 | new DiceRule => new \Dice\Rule 365 | ``` 366 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "level-2/dice", 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.", 4 | "license": "BSD-2-Clause", 5 | "homepage": "http://r.je/dice.html", 6 | "keywords": ["Dependency injection", "ioc", "Dependency injection container", "DI"], 7 | "authors": [ 8 | { 9 | "name": "Tom Butler", 10 | "email": "tom@r.je" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=7.0.0" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "Dice\\": "./" 19 | } 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "^6.5" 23 | }, 24 | "scripts": { 25 | "test": "phpunit" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tests 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/BasicTest.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | 8 | 9 | 10 | 11 | class BasicTest extends DiceTest { 12 | 13 | public function testCreate() { 14 | $this->getMockBuilder('TestCreate')->getMock(); 15 | $myobj = $this->dice->create('TestCreate'); 16 | $this->assertInstanceOf('TestCreate', $myobj); 17 | } 18 | 19 | 20 | 21 | public function testCreateInvalid() { 22 | //"can't expect default exception". Not sure why. 23 | $this->expectException('ErrorException'); 24 | try { 25 | $this->dice->create('SomeClassThatDoesNotExist'); 26 | } 27 | catch (Exception $e) { 28 | throw new ErrorException('Error occurred'); 29 | } 30 | } 31 | 32 | public function testNoConstructor() { 33 | $a = $this->dice->create('NoConstructor'); 34 | $this->assertInstanceOf('NoConstructor', $a); 35 | } 36 | 37 | 38 | public function testSetDefaultRule() { 39 | $defaultBehaviour = []; 40 | $defaultBehaviour['shared'] = true; 41 | $dice = $this->dice->addRule('*', $defaultBehaviour); 42 | 43 | $rule = $dice->getRule('*'); 44 | foreach ($defaultBehaviour as $name => $value) { 45 | $this->assertEquals($rule[$name], $defaultBehaviour[$name]); 46 | } 47 | } 48 | 49 | 50 | public function testDefaultRuleWorks() { 51 | $defaultBehaviour = []; 52 | $defaultBehaviour['shared'] = true; 53 | 54 | $dice = $this->dice->addRule('*', $defaultBehaviour); 55 | 56 | $rule = $dice->getRule('A'); 57 | 58 | $this->assertTrue($rule['shared']); 59 | 60 | $a1 = $dice->create('A'); 61 | $a2 = $dice->create('A'); 62 | 63 | $this->assertSame($a1, $a2); 64 | } 65 | 66 | 67 | /* 68 | * Object graph creation cannot be tested with mocks because the constructor need to be tested. 69 | * You can't set 'expects' on the objects which are created making them redundant for that as well 70 | * Need real classes to test with unfortunately. 71 | */ 72 | public function testObjectGraphCreation() { 73 | $a = $this->dice->create('A'); 74 | $this->assertInstanceOf('B', $a->b); 75 | $this->assertInstanceOf('c', $a->b->c); 76 | $this->assertInstanceOf('D', $a->b->c->d); 77 | $this->assertInstanceOf('E', $a->b->c->e); 78 | $this->assertInstanceOf('F', $a->b->c->e->f); 79 | } 80 | 81 | public function testSharedNamed() { 82 | $rule = []; 83 | $rule['shared'] = true; 84 | $rule['instanceOf'] = 'A'; 85 | 86 | $dice = $this->dice->addRule('[A]', $rule); 87 | 88 | $a1 = $dice->create('[A]'); 89 | $a2 = $dice->create('[A]'); 90 | $this->assertSame($a1, $a2); 91 | } 92 | 93 | public function testSharedRule() { 94 | $shared = []; 95 | $shared['shared'] = true; 96 | 97 | $dice = $this->dice->addRule('MyObj', $shared); 98 | 99 | $obj = $dice->create('MyObj'); 100 | $this->assertInstanceOf('MyObj', $obj); 101 | 102 | $obj2 = $dice->create('MyObj'); 103 | $this->assertInstanceOf('MyObj', $obj2); 104 | 105 | $this->assertSame($obj, $obj2); 106 | 107 | 108 | //This check isn't strictly needed but it's nice to have that safety measure! 109 | $obj->setFoo('bar'); 110 | $this->assertEquals($obj->getFoo(), $obj2->getFoo()); 111 | $this->assertEquals($obj->getFoo(), 'bar'); 112 | $this->assertEquals($obj2->getFoo(), 'bar'); 113 | } 114 | 115 | 116 | public function testInterfaceRule() { 117 | $rule = []; 118 | 119 | $rule['shared'] = true; 120 | $dice = $this->dice->addRule('interfaceTest', $rule); 121 | 122 | $one = $dice->create('InterfaceTestClass'); 123 | $two = $dice->create('InterfaceTestClass'); 124 | 125 | $this->assertSame($one, $two); 126 | } 127 | 128 | public function testCyclicReferences() { 129 | $rule = []; 130 | $rule['shared'] = true; 131 | 132 | $dice = $this->dice->addRule('CyclicB', $rule); 133 | 134 | $a = $dice->create('CyclicA'); 135 | 136 | $this->assertInstanceOf('CyclicB', $a->b); 137 | $this->assertInstanceOf('CyclicA', $a->b->a); 138 | 139 | $this->assertSame($a->b, $a->b->a->b); 140 | } 141 | 142 | public function testInherit() { 143 | $rule = ['shared' => true, 'inherit' => false]; 144 | 145 | $dice = $this->dice->addRule('ParentClass', $rule); 146 | $obj1 = $dice->create('Child'); 147 | $obj2 = $dice->create('Child'); 148 | $this->assertNotSame($obj1, $obj2); 149 | } 150 | 151 | public function testSharedOverride() { 152 | 153 | //Set everything to shared by default 154 | $dice = $this->dice->addRule('*', ['shared' => true]); 155 | 156 | $dice = $dice->addRule('A', ['shared' => false]); 157 | 158 | $a1 = $dice->create('A'); 159 | $a2 = $dice->create('A'); 160 | 161 | $this->assertNotSame($a1, $a2); 162 | 163 | } 164 | 165 | public function testOptionalInterface() { 166 | 167 | $optionalInterface = $this->dice->create('OptionalInterface'); 168 | 169 | $this->assertEquals(null, $optionalInterface->obj); 170 | } 171 | 172 | 173 | public function testScalarTypeHintWithShareInstances() { 174 | 175 | $dice = $this->dice->addRule('ScalarTypeHint', ['shareInstances' => ['A']]); 176 | 177 | $obj = $dice->create('ScalarTypeHint'); 178 | 179 | $this->assertInstanceOf('ScalarTypeHint', $obj); 180 | } 181 | 182 | public function testPassGlobals() { 183 | //write to the global $_GET variable 184 | $_GET['foo'] = 'bar'; 185 | 186 | $dice = $this->dice->addRule('CheckConstructorArgs', 187 | [ 188 | 'constructParams' => [ 189 | [\Dice\Dice::GLOBAL => '_GET'] 190 | ] 191 | ]); 192 | 193 | $obj = $dice->create('CheckConstructorArgs'); 194 | 195 | $this->assertEquals($_GET, $obj->arg1); 196 | } 197 | 198 | public function testPassConstantString() { 199 | $dice = $this->dice->addRule('CheckConstructorArgs', 200 | [ 201 | 'constructParams' => [ 202 | [\Dice\Dice::CONSTANT => '\PDO::FETCH_ASSOC'] 203 | ] 204 | ]); 205 | 206 | $obj = $dice->create('CheckConstructorArgs'); 207 | 208 | $this->assertEquals(\PDO::FETCH_ASSOC, $obj->arg1); 209 | } 210 | 211 | public function testImmutability() { 212 | $this->assertEquals([], $this->dice->getRule('Foo')); 213 | 214 | $dice = $this->dice->addRule('Foo', ['shared' => true]); 215 | 216 | $this->assertEquals([], $this->dice->getRule('Foo')); 217 | } 218 | 219 | public function testPassSelf() { 220 | $dice = $this->dice->addRule('CheckConstructorArgs', 221 | [ 222 | 'constructParams' => [ 223 | [\Dice\Dice::INSTANCE => \Dice\Dice::SELF] 224 | ] 225 | ]); 226 | 227 | $obj = $dice->create('CheckConstructorArgs'); 228 | 229 | $this->assertEquals($dice, $obj->arg1); 230 | } 231 | 232 | 233 | // Issue 180 234 | public function testSlashNoSlash() { 235 | $dice = $this->dice->addRule('\someclass', ['shared' => true]); 236 | 237 | $b = $dice->create('\someotherclass'); 238 | $a = $dice->create('\someclass'); 239 | 240 | $this->assertSame($a, $b->obj); 241 | } 242 | } -------------------------------------------------------------------------------- /tests/CallTest.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | class CallTest extends DiceTest { 8 | public function testCall() { 9 | $rule = []; 10 | $rule['call'][] = array('callMe', array()); 11 | $dice = $this->dice->addRule('TestCall', $rule); 12 | $object = $dice->create('TestCall'); 13 | $this->assertTrue($object->isCalled); 14 | } 15 | 16 | public function testCallWithParameters() { 17 | $rule = []; 18 | $rule['call'][] = array('callMe', array('one', 'two')); 19 | $dice = $this->dice->addRule('TestCall2', $rule); 20 | $object = $dice->create('TestCall2'); 21 | $this->assertEquals('one', $object->foo); 22 | $this->assertEquals('two', $object->bar); 23 | } 24 | 25 | public function testCallWithInstance() { 26 | $rule = []; 27 | $rule['call'][] = array('callMe', array([\Dice\Dice::INSTANCE => 'A'])); 28 | $dice = $this->dice->addRule('TestCall3', $rule); 29 | $object = $dice->create('TestCall3'); 30 | 31 | $this->assertInstanceOf('a', $object->a); 32 | 33 | } 34 | 35 | public function testCallAutoWireInstance() { 36 | $rule = []; 37 | $rule['call'][] = array('callMe', []); 38 | $dice = $this->dice->addRule('TestCall3', $rule); 39 | $object = $dice->create('TestCall3'); 40 | 41 | $this->assertInstanceOf('a', $object->a); 42 | } 43 | 44 | public function testCallReturnValue() { 45 | $rule = []; 46 | 47 | $returnValue = null; 48 | 49 | $rule['call'][] = array('callMe', [], function($return) use (&$returnValue) { 50 | $returnValue = $return; 51 | }); 52 | 53 | 54 | $dice = $this->dice->addRule('TestCall3', $rule); 55 | $object = $dice->create('TestCall3'); 56 | 57 | $this->assertInstanceOf('a', $object->a); 58 | $this->assertEquals('callMe called', $returnValue); 59 | } 60 | 61 | 62 | public function testCallChain() { 63 | $rules = [ 64 | 'TestCallImmutable' => [ 65 | 'call' => [ 66 | ['call1', ['foo'], \Dice\Dice::CHAIN_CALL], 67 | ['call2', ['bar'], \Dice\Dice::CHAIN_CALL] 68 | ] 69 | ] 70 | ]; 71 | 72 | $dice = $this->dice->addRules($rules); 73 | 74 | $object = $dice->create('TestCallImmutable'); 75 | 76 | $this->assertEquals('foo', $object->a); 77 | $this->assertEquals('bar', $object->b); 78 | } 79 | 80 | public function testCallShareVariadic() { 81 | // Shared params should not be passed to variadic call 82 | 83 | $rules = [ 84 | 'TestCallVariadic' => [ 85 | 'call' => [ 86 | ['callMe', ['test1']] 87 | ] 88 | ] 89 | ]; 90 | 91 | $dice = $this->dice->addRules($rules); 92 | $object = $dice->create('TestCallVariadic', [], [new F()]); 93 | 94 | $this->assertEquals(['test1'], $object->data); 95 | } 96 | } -------------------------------------------------------------------------------- /tests/ChainTest.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | class ChainTest extends DiceTest { 8 | public function testChainCall() { 9 | $dice = $this->dice->addRules([ 10 | '$someClass' => [ 11 | 'instanceOf' => 'Factory', 12 | 'call' => [ 13 | ['get', [], \Dice\Dice::CHAIN_CALL] 14 | ] 15 | ] 16 | ]); 17 | 18 | $obj = $dice->create('$someClass'); 19 | 20 | $this->assertInstanceOf('FactoryDependency', $obj); 21 | 22 | } 23 | 24 | public function testMultipleChainCall() { 25 | $dice = $this->dice->addRules([ 26 | '$someClass' => [ 27 | 'instanceOf' => 'Factory', 28 | 'call' => [ 29 | ['get', [], \Dice\Dice::CHAIN_CALL], 30 | ['getBar', [], \Dice\Dice::CHAIN_CALL] 31 | ] 32 | ] 33 | ]); 34 | 35 | $obj = $dice->create('$someClass'); 36 | 37 | $this->assertEquals('bar', $obj); 38 | 39 | } 40 | 41 | public function testChainCallShared() { 42 | $dice = $this->dice->addRules([ 43 | '$someClass' => [ 44 | 'shared' => true, 45 | 'instanceOf' => 'Factory', 46 | 'call' => [ 47 | ['get', [], \Dice\Dice::CHAIN_CALL] 48 | ] 49 | ] 50 | ]); 51 | 52 | $obj = $dice->create('$someClass'); 53 | 54 | $this->assertInstanceOf('FactoryDependency', $obj); 55 | } 56 | 57 | 58 | public function testChainCallInject() { 59 | $dice = $this->dice->addRules([ 60 | 'FactoryDependency' => [ 61 | 'instanceOf' => 'Factory', 62 | 'call' => [ 63 | ['get', [], \Dice\Dice::CHAIN_CALL] 64 | ] 65 | ] 66 | ]); 67 | 68 | $obj = $dice->create('RequiresFactoryDependecy'); 69 | 70 | $this->assertInstanceOf('FactoryDependency', $obj->dep); 71 | } 72 | 73 | public function testChainCallInjectShared() { 74 | $dice = $this->dice->addRules([ 75 | 'FactoryDependency' => [ 76 | 'shared' => true, 77 | 'instanceOf' => 'Factory', 78 | 'call' => [ 79 | ['get', [], \Dice\Dice::CHAIN_CALL] 80 | ] 81 | ] 82 | ]); 83 | 84 | 85 | $dice->create('FactoryDependency'); 86 | 87 | $obj = $dice->create('RequiresFactoryDependecy'); 88 | 89 | $this->assertInstanceOf('FactoryDependency', $obj->dep); 90 | 91 | $obj2 = $dice->create('RequiresFactoryDependecy'); 92 | 93 | 94 | $this->assertNotSame($obj, $obj2); 95 | $this->assertSame($obj->dep, $obj2->dep); 96 | } 97 | 98 | } 99 | 100 | 101 | class Factory { 102 | public function get() { 103 | return new FactoryDependency; 104 | } 105 | } 106 | 107 | class FactoryDependency { 108 | public function getBar() { 109 | return 'bar'; 110 | } 111 | } 112 | 113 | class RequiresFactoryDependecy { 114 | public $dep; 115 | 116 | public function __construct(FactoryDependency $dep) { 117 | $this->dep = $dep; 118 | } 119 | } -------------------------------------------------------------------------------- /tests/ConstructParamsTest.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | class ConstructParamsTest extends DiceTest { 8 | 9 | public function testConstructParams() { 10 | $rule = []; 11 | $rule['constructParams'] = array('foo', 'bar'); 12 | $dice = $this->dice->addRule('RequiresConstructorArgsA', $rule); 13 | 14 | $obj = $dice->create('RequiresConstructorArgsA'); 15 | 16 | $this->assertEquals($obj->foo, 'foo'); 17 | $this->assertEquals($obj->bar, 'bar'); 18 | } 19 | 20 | 21 | public function testInternalClass() { 22 | $rule = []; 23 | $rule['constructParams'][] = '.'; 24 | 25 | $dice = $this->dice->addRule('DirectoryIterator', $rule); 26 | 27 | $dir = $dice->create('DirectoryIterator'); 28 | 29 | $this->assertInstanceOf('DirectoryIterator', $dir); 30 | } 31 | 32 | public function testInternalClassExtended() { 33 | $rule = []; 34 | $rule['constructParams'][] = '.'; 35 | 36 | $dice = $this->dice->addRule('MyDirectoryIterator', $rule); 37 | 38 | $dir = $dice->create('MyDirectoryIterator'); 39 | 40 | $this->assertInstanceOf('MyDirectoryIterator', $dir); 41 | } 42 | 43 | 44 | public function testInternalClassExtendedConstructor() { 45 | $rule = []; 46 | $rule['constructParams'][] = '.'; 47 | 48 | $dice = $this->dice->addRule('MyDirectoryIterator2', $rule); 49 | 50 | $dir = $dice->create('MyDirectoryIterator2'); 51 | 52 | $this->assertInstanceOf('MyDirectoryIterator2', $dir); 53 | } 54 | 55 | public function testDefaultNullAssigned() { 56 | $rule = []; 57 | $rule['constructParams'] = [ [\Dice\Dice::INSTANCE => 'A'], null]; 58 | $dice = $this->dice->addRule('MethodWithDefaultNull', $rule); 59 | $obj = $dice->create('MethodWithDefaultNull'); 60 | $this->assertNull($obj->b); 61 | } 62 | 63 | public function testConstructParamsNested() { 64 | $rule = []; 65 | $rule['constructParams'] = array('foo', 'bar'); 66 | $dice = $this->dice->addRule('RequiresConstructorArgsA', $rule); 67 | 68 | $rule = []; 69 | $rule['shareInstances'] = array('D'); 70 | $dice = $dice->addRule('ParamRequiresArgs', $rule); 71 | 72 | $obj = $dice->create('ParamRequiresArgs'); 73 | 74 | $this->assertEquals($obj->a->foo, 'foo'); 75 | $this->assertEquals($obj->a->bar, 'bar'); 76 | } 77 | 78 | 79 | public function testConstructParamsMixed() { 80 | $rule = []; 81 | $rule['constructParams'] = array('foo', 'bar'); 82 | $dice = $this->dice->addRule('RequiresConstructorArgsB', $rule); 83 | 84 | $obj = $dice->create('RequiresConstructorArgsB'); 85 | 86 | $this->assertEquals($obj->foo, 'foo'); 87 | $this->assertEquals($obj->bar, 'bar'); 88 | $this->assertInstanceOf('A', $obj->a); 89 | } 90 | 91 | 92 | public function testSharedClassWithTraitExtendsInternalClass() { 93 | $rule = []; 94 | $rule['constructParams'] = ['.']; 95 | 96 | $dice = $this->dice->addRule('MyDirectoryIteratorWithTrait', $rule); 97 | 98 | $dir = $dice->create('MyDirectoryIteratorWithTrait'); 99 | 100 | $this->assertInstanceOf('MyDirectoryIteratorWithTrait', $dir); 101 | } 102 | 103 | public function testConstructParamsPrecedence() { 104 | $rule = []; 105 | $rule['constructParams'] = ['A', 'B']; 106 | $dice = $this->dice->addRule('RequiresConstructorArgsA', $rule); 107 | 108 | $a1 = $dice->create('RequiresConstructorArgsA'); 109 | $this->assertEquals('A', $a1->foo); 110 | $this->assertEquals('B', $a1->bar); 111 | 112 | $a2 = $dice->create('RequiresConstructorArgsA', ['C', 'D']); 113 | $this->assertEquals('C', $a2->foo); 114 | $this->assertEquals('D', $a2->bar); 115 | } 116 | 117 | public function testNullScalar() { 118 | $rule = []; 119 | $rule['constructParams'] = [null]; 120 | $dice = $this->dice->addRule('NullScalar', $rule); 121 | 122 | $obj = $dice->create('NullScalar'); 123 | $this->assertEquals(null, $obj->string); 124 | } 125 | 126 | public function testNullScalarNested() { 127 | $rule = []; 128 | $rule['constructParams'] = [null]; 129 | $dice = $this->dice->addRule('NullScalar', $rule); 130 | 131 | $obj = $dice->create('NullScalarNested'); 132 | $this->assertEquals(null, $obj->nullScalar->string); 133 | } 134 | 135 | public function testNullableClassTypeHint() { 136 | $nullableClassTypeHint = $this->dice->create('NullableClassTypeHint'); 137 | 138 | $this->assertEquals(null, $nullableClassTypeHint->obj); 139 | } 140 | 141 | } -------------------------------------------------------------------------------- /tests/CreateArgsTest.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | class CreateArgsTest extends DiceTest { 8 | 9 | public function testConsumeArgs() { 10 | $rule = []; 11 | $rule['constructParams'] = ['A']; 12 | $dice = $this->dice->addRule('ConsumeArgsSub', $rule); 13 | $foo = $dice->create('ConsumeArgsTop',['B']); 14 | 15 | $this->assertEquals('A', $foo->a->s); 16 | } 17 | 18 | 19 | public function testConstructArgs() { 20 | $obj = $this->dice->create('RequiresConstructorArgsA', array('foo', 'bar')); 21 | $this->assertEquals($obj->foo, 'foo'); 22 | $this->assertEquals($obj->bar, 'bar'); 23 | } 24 | 25 | public function testConstructArgsMixed() { 26 | $obj = $this->dice->create('RequiresConstructorArgsB', array('foo', 'bar')); 27 | $this->assertEquals($obj->foo, 'foo'); 28 | $this->assertEquals($obj->bar, 'bar'); 29 | $this->assertInstanceOf('A', $obj->a); 30 | } 31 | 32 | public function testCreateArgs1() { 33 | $a = $this->dice->create('A', array($this->dice->create('ExtendedB'))); 34 | $this->assertInstanceOf('ExtendedB', $a->b); 35 | } 36 | 37 | 38 | public function testCreateArgs2() { 39 | $a2 = $this->dice->create('A2', array($this->dice->create('ExtendedB'), 'Foo')); 40 | $this->assertInstanceOf('B', $a2->b); 41 | $this->assertInstanceOf('C', $a2->c); 42 | $this->assertEquals($a2->foo, 'Foo'); 43 | } 44 | 45 | 46 | public function testCreateArgs3() { 47 | //reverse order args. It should be smart enough to handle this. 48 | $a2 = $this->dice->create('A2', array('Foo', $this->dice->create('ExtendedB'))); 49 | $this->assertInstanceOf('B', $a2->b); 50 | $this->assertInstanceOf('C', $a2->c); 51 | $this->assertEquals($a2->foo, 'Foo'); 52 | } 53 | 54 | public function testCreateArgs4() { 55 | $a2 = $this->dice->create('A3', array('Foo', $this->dice->create('ExtendedB'))); 56 | $this->assertInstanceOf('B', $a2->b); 57 | $this->assertInstanceOf('C', $a2->c); 58 | $this->assertEquals($a2->foo, 'Foo'); 59 | } 60 | 61 | public function testBestMatch() { 62 | $bestMatch = $this->dice->create('BestMatch', array('foo', $this->dice->create('A'))); 63 | $this->assertEquals('foo', $bestMatch->string); 64 | $this->assertInstanceOf('A', $bestMatch->a); 65 | } 66 | 67 | public function testTwoDefaultNullClass() { 68 | $obj = $this->dice->create('MethodWithTwoDefaultNullC'); 69 | $this->assertNull($obj->a); 70 | $this->assertInstanceOf('NB',$obj->b); 71 | } 72 | 73 | public function testTwoDefaultNullClassClass() { 74 | $obj = $this->dice->create('MethodWithTwoDefaultNullCC'); 75 | $this->assertNull($obj->a); 76 | $this->assertInstanceOf('NB',$obj->b); 77 | $this->assertInstanceOf('NC',$obj->c); 78 | } 79 | 80 | public function testScalarConstructorArgs() { 81 | $obj = $this->dice->create('ScalarConstructors', ['string', null]); 82 | $this->assertEquals('string', $obj->string); 83 | $this->assertEquals(null, $obj->null); 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /tests/DiceTest.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | abstract class DiceTest extends \PHPUnit\Framework\TestCase { 8 | protected $dice; 9 | 10 | public function __construct() { 11 | parent::__construct(); 12 | // spl_autoload_register(array($this, 'autoload')); 13 | 14 | //Load the test classes for this test 15 | $name = str_replace('Test', '', get_class($this)); 16 | require_once 'tests/TestData/Basic.php'; 17 | 18 | if (file_exists('tests/TestData/' . $name . '.php')) { 19 | require_once 'tests/TestData/' . $name . '.php'; 20 | } 21 | } 22 | 23 | public function autoload($class) { 24 | //If Dice Triggers the autoloader the test fails 25 | //This generally means something invalid has been passed to 26 | //a method such as is_subclass_of or dice is trying to construct 27 | //an object from something it shouldn't. 28 | $this->fail('Autoload triggered: ' . $class); 29 | } 30 | 31 | protected function setUp(): void { 32 | parent::setUp (); 33 | $this->dice = new \Dice\Dice(); 34 | } 35 | 36 | protected function tearDown(): void { 37 | $this->dice = null; 38 | parent::tearDown (); 39 | } 40 | } -------------------------------------------------------------------------------- /tests/NamedInstancesTest.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | class NamedInstancesTest extends DiceTest { 8 | public function testMultipleSharedInstancesByNameMixed() { 9 | $rule = []; 10 | $rule['shared'] = true; 11 | $rule['constructParams'][] = 'FirstY'; 12 | 13 | $dice = $this->dice->addRule('Y', $rule); 14 | 15 | $rule = []; 16 | $rule['instanceOf'] = 'Y'; 17 | $rule['shared'] = true; 18 | $rule['inherit'] = false; 19 | $rule['constructParams'][] = 'SecondY'; 20 | 21 | $dice = $dice->addRule('[Y2]', $rule); 22 | 23 | $rule = []; 24 | $rule['constructParams'] = [ [\Dice\Dice::INSTANCE => 'Y'], [\Dice\Dice::INSTANCE => '[Y2]']]; 25 | 26 | $dice = $dice->addRule('Z', $rule); 27 | 28 | $z = $dice->create('Z'); 29 | $this->assertEquals($z->y1->name, 'FirstY'); 30 | $this->assertEquals($z->y2->name, 'SecondY'); 31 | } 32 | 33 | public function testNonSharedComponentByNameA() { 34 | $rule = []; 35 | $rule['instanceOf'] = 'ExtendedB'; 36 | $dice = $this->dice->addRule('$B', $rule); 37 | 38 | $rule = []; 39 | $rule['constructParams'][] = [\Dice\Dice::INSTANCE => '$B']; 40 | $dice = $dice->addRule('A', $rule); 41 | 42 | $a = $dice->create('A'); 43 | $this->assertInstanceOf('ExtendedB', $a->b); 44 | } 45 | 46 | public function testNonSharedComponentByName() { 47 | 48 | $rule = []; 49 | $rule['instanceOf'] = 'Y3'; 50 | $rule['constructParams'][] = 'test'; 51 | 52 | 53 | $dice = $this->dice->addRule('$Y2', $rule); 54 | 55 | 56 | $y2 = $dice->create('$Y2'); 57 | //echo $y2->name; 58 | $this->assertInstanceOf('Y3', $y2); 59 | 60 | $rule = []; 61 | 62 | $rule['constructParams'][] = [\Dice\Dice::INSTANCE => '$Y2']; 63 | $dice = $dice->addRule('Y1', $rule); 64 | 65 | $y1 = $dice->create('Y1'); 66 | $this->assertInstanceOf('Y3', $y1->y2); 67 | } 68 | 69 | public function testSubstitutionByName() { 70 | $rule = []; 71 | $rule['instanceOf'] = 'ExtendedB'; 72 | $dice = $this->dice->addRule('$B', $rule); 73 | 74 | $rule = []; 75 | $rule['substitutions']['B'] = [\Dice\Dice::INSTANCE => '$B']; 76 | 77 | $dice = $dice->addRule('A', $rule); 78 | $a = $dice->create('A'); 79 | 80 | $this->assertInstanceOf('ExtendedB', $a->b); 81 | } 82 | 83 | public function testMultipleSubstitutions() { 84 | $rule = []; 85 | $rule['instanceOf'] = 'Y2'; 86 | $rule['constructParams'][] = 'first'; 87 | $dice = $this->dice->addRule('$Y2A', $rule); 88 | 89 | $rule = []; 90 | $rule['instanceOf'] = 'Y2'; 91 | $rule['constructParams'][] = 'second'; 92 | $dice = $dice->addRule('$Y2B', $rule); 93 | 94 | $rule = []; 95 | $rule['constructParams'] = array([\Dice\Dice::INSTANCE => '$Y2A'], [\Dice\Dice::INSTANCE => '$Y2B']); 96 | $dice = $dice->addRule('HasTwoSameDependencies', $rule); 97 | 98 | $twodep = $dice->create('HasTwoSameDependencies'); 99 | 100 | $this->assertEquals('first', $twodep->y2a->name); 101 | $this->assertEquals('second', $twodep->y2b->name); 102 | } 103 | 104 | public function testNamedInstanceCallWithInheritance() { 105 | $rule1 = []; 106 | $rule1['call'] = [ 107 | ['callMe', [1, 3] ], 108 | ['callMe', [3, 4] ] 109 | ]; 110 | 111 | $dice = $this->dice->addRule('Y', $rule1); 112 | 113 | $rule2 = []; 114 | $rule2['instanceOf'] = 'Y'; 115 | $rule2['constructParams'] = ['Foo']; 116 | 117 | $dice = $dice->addRule('$MyInstance', $rule2); 118 | 119 | $this->assertEquals(array_merge_recursive($rule1, $rule2), $dice->getRule('$MyInstance')); 120 | 121 | } 122 | 123 | public function testNamedInstanceCallWithoutInheritance() { 124 | $rule1 = []; 125 | $rule1['call'] = [ 126 | ['callMe', [1, 3] ], 127 | ['callMe', [3, 4] ] 128 | ]; 129 | 130 | $dice = $this->dice->addRule('Y', $rule1); 131 | 132 | $rule2 = []; 133 | $rule2['instanceOf'] = 'Y'; 134 | $rule2['inherit'] = false; 135 | $rule2['constructParams'] = ['Foo']; 136 | 137 | $dice = $dice->addRule('$MyInstance', $rule2); 138 | 139 | $this->assertEquals($rule2, $dice->getRule('$MyInstance')); 140 | } 141 | 142 | } -------------------------------------------------------------------------------- /tests/NamespaceTest.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | class NamespaceTest extends DiceTest { 8 | public function testNamespaceBasic() { 9 | $a = $this->dice->create('Foo\\A'); 10 | $this->assertInstanceOf('Foo\\A', $a); 11 | } 12 | 13 | 14 | public function testNamespaceWithSlash() { 15 | $a = $this->dice->create('\\Foo\\A'); 16 | $this->assertInstanceOf('\\Foo\\A', $a); 17 | } 18 | 19 | public function testNamespaceWithSlashrule() { 20 | $rule = []; 21 | $rule['substitutions']['Foo\\A'] = [\Dice\Dice::INSTANCE => 'Foo\\ExtendedA']; 22 | $dice = $this->dice->addRule('\\Foo\\B', $rule); 23 | 24 | $b = $dice->create('\\Foo\\B'); 25 | $this->assertInstanceOf('Foo\\ExtendedA', $b->a); 26 | } 27 | 28 | public function testNamespaceWithSlashruleInstance() { 29 | $rule = []; 30 | $rule['substitutions']['Foo\\A'] = [\Dice\Dice::INSTANCE => 'Foo\\ExtendedA']; 31 | $dice = $this->dice->addRule('\\Foo\\B', $rule); 32 | 33 | $b = $dice->create('\\Foo\\B'); 34 | $this->assertInstanceOf('Foo\\ExtendedA', $b->a); 35 | } 36 | 37 | public function testNamespaceTypeHint() { 38 | $rule = []; 39 | $rule['shared'] = true; 40 | $dice = $this->dice->addRule('Bar\\A', $rule); 41 | 42 | $c = $dice->create('Foo\\C'); 43 | $this->assertInstanceOf('Bar\\A', $c->a); 44 | 45 | $c2 = $dice->create('Foo\\C'); 46 | $this->assertNotSame($c, $c2); 47 | 48 | //Check the rule has been correctly recognised for type hinted classes in a different namespace 49 | $this->assertSame($c2->a, $c->a); 50 | } 51 | 52 | public function testNamespaceInjection() { 53 | $b = $this->dice->create('Foo\\B'); 54 | $this->assertInstanceOf('Foo\\B', $b); 55 | $this->assertInstanceOf('Foo\\A', $b->a); 56 | } 57 | 58 | 59 | public function testNamespaceRuleSubstitution() { 60 | $rule = []; 61 | $rule['substitutions']['Foo\\A'] = [\Dice\Dice::INSTANCE => 'Foo\\ExtendedA']; 62 | $dice = $this->dice->addRule('Foo\\B', $rule); 63 | 64 | $b = $dice->create('Foo\\B'); 65 | $this->assertInstanceOf('Foo\\ExtendedA', $b->a); 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /tests/ShareInstancesTest.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | class ShareInstancesTest extends DiceTest { 8 | public function testShareInstances() { 9 | $rule = []; 10 | $rule['shareInstances'] = ['Shared']; 11 | $dice = $this->dice->addRule('TestSharedInstancesTop', $rule); 12 | 13 | 14 | $shareTest = $dice->create('TestSharedInstancesTop'); 15 | 16 | $this->assertinstanceOf('TestSharedInstancesTop', $shareTest); 17 | 18 | $this->assertInstanceOf('SharedInstanceTest1', $shareTest->share1); 19 | $this->assertInstanceOf('SharedInstanceTest2', $shareTest->share2); 20 | 21 | $this->assertSame($shareTest->shared, $shareTest->share1->shared); 22 | $this->assertSame($shareTest->share1->shared, $shareTest->share2->shared); 23 | $this->assertEquals($shareTest->shared->uniq, $shareTest->share1->shared->uniq); 24 | $this->assertEquals($shareTest->share1->shared->uniq, $shareTest->share2->shared->uniq); 25 | 26 | } 27 | 28 | public function testNamedShareInstances() { 29 | 30 | $rule = []; 31 | $rule['instanceOf'] = 'Shared'; 32 | $dice = $this->dice->addRule('$Shared', $rule); 33 | 34 | $rule = []; 35 | $rule['shareInstances'] = ['$Shared']; 36 | $dice = $dice->addRule('TestSharedInstancesTop', $rule); 37 | 38 | 39 | $shareTest = $dice->create('TestSharedInstancesTop'); 40 | 41 | $this->assertinstanceOf('TestSharedInstancesTop', $shareTest); 42 | 43 | $this->assertInstanceOf('SharedInstanceTest1', $shareTest->share1); 44 | $this->assertInstanceOf('SharedInstanceTest2', $shareTest->share2); 45 | 46 | $this->assertSame($shareTest->shared, $shareTest->share1->shared); 47 | $this->assertSame($shareTest->share1->shared, $shareTest->share2->shared); 48 | $this->assertEquals($shareTest->shared->uniq, $shareTest->share1->shared->uniq); 49 | $this->assertEquals($shareTest->share1->shared->uniq, $shareTest->share2->shared->uniq); 50 | 51 | 52 | $shareTest2 = $dice->create('TestSharedInstancesTop'); 53 | $this->assertNotSame($shareTest2->share1->shared, $shareTest->share2->shared); 54 | } 55 | 56 | 57 | public function testShareInstancesNested() { 58 | $rule = []; 59 | $rule['shareInstances'] = ['F']; 60 | $dice = $this->dice->addRule('A4',$rule); 61 | $a = $dice->create('A4'); 62 | $this->assertTrue($a->m1->f === $a->m2->e->f); 63 | } 64 | 65 | 66 | public function testShareInstancesMultiple() { 67 | $rule = []; 68 | $rule['shareInstances'] = ['Shared']; 69 | $dice = $this->dice->addRule('TestSharedInstancesTop', $rule); 70 | 71 | 72 | $shareTest = $dice->create('TestSharedInstancesTop'); 73 | 74 | $this->assertinstanceOf('TestSharedInstancesTop', $shareTest); 75 | 76 | $this->assertInstanceOf('SharedInstanceTest1', $shareTest->share1); 77 | $this->assertInstanceOf('SharedInstanceTest2', $shareTest->share2); 78 | 79 | $this->assertSame($shareTest->shared, $shareTest->share1->shared); 80 | $this->assertSame($shareTest->share1->shared, $shareTest->share2->shared); 81 | $this->assertEquals($shareTest->shared->uniq, $shareTest->share1->shared->uniq); 82 | $this->assertEquals($shareTest->share1->shared->uniq, $shareTest->share2->shared->uniq); 83 | 84 | 85 | $shareTest2 = $dice->create('TestSharedInstancesTop'); 86 | $this->assertSame($shareTest2->shared, $shareTest2->share1->shared); 87 | $this->assertSame($shareTest2->share1->shared, $shareTest2->share2->shared); 88 | $this->assertEquals($shareTest2->shared->uniq, $shareTest2->share1->shared->uniq); 89 | $this->assertEquals($shareTest2->share1->shared->uniq, $shareTest2->share2->shared->uniq); 90 | 91 | $this->assertNotSame($shareTest->shared, $shareTest2->shared); 92 | $this->assertNotSame($shareTest->share1->shared, $shareTest2->share2->shared); 93 | $this->assertNotEquals($shareTest->shared->uniq, $shareTest2->shared->uniq); 94 | $this->assertNotEquals($shareTest->share1->shared->uniq, $shareTest2->share2->shared->uniq); 95 | 96 | } 97 | } -------------------------------------------------------------------------------- /tests/SubstitutionsTest.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | class SubstitutionsTest extends DiceTest { 8 | public function testNoMoreAssign() { 9 | $rule = []; 10 | $rule['substitutions']['Bar77'] = [\Dice\Dice::INSTANCE => function() { 11 | return Baz77::create(); 12 | }]; 13 | 14 | $dice = $this->dice->addRule('Foo77', $rule); 15 | 16 | $foo = $dice->create('Foo77'); 17 | 18 | $this->assertInstanceOf('Bar77', $foo->bar); 19 | $this->assertEquals('Z', $foo->bar->a); 20 | } 21 | 22 | public function testNullSubstitution() { 23 | $rule = []; 24 | $rule['substitutions']['B'] = null; 25 | $dice = $this->dice->addRule('MethodWithDefaultNull', $rule); 26 | $obj = $dice->create('MethodWithDefaultNull'); 27 | $this->assertNull($obj->b); 28 | } 29 | 30 | public function testSubstitutionText() { 31 | $rule = []; 32 | $rule['substitutions']['B'] = [\Dice\Dice::INSTANCE => 'ExtendedB']; 33 | $dice = $this->dice->addRule('A', $rule); 34 | 35 | $a = $dice->create('A'); 36 | 37 | $this->assertInstanceOf('ExtendedB', $a->b); 38 | } 39 | 40 | public function testSubstitutionTextMixedCase() { 41 | $rule = []; 42 | $rule['substitutions']['B'] = [\Dice\Dice::INSTANCE => 'exTenDedb']; 43 | $dice = $this->dice->addRule('A', $rule); 44 | 45 | $a = $dice->create('A'); 46 | 47 | $this->assertInstanceOf('ExtendedB', $a->b); 48 | } 49 | 50 | public function testSubstitutionCallback() { 51 | $rule = []; 52 | $injection = $this->dice; 53 | $rule['substitutions']['B'] = [\Dice\Dice::INSTANCE => function() use ($injection) { 54 | return $injection->create('ExtendedB'); 55 | }]; 56 | 57 | $dice = $this->dice->addRule('A', $rule); 58 | 59 | $a = $dice->create('A'); 60 | 61 | $this->assertInstanceOf('ExtendedB', $a->b); 62 | } 63 | 64 | 65 | public function testSubstitutionObject() { 66 | $rule = []; 67 | 68 | $rule['substitutions']['B'] = $this->dice->create('ExtendedB'); 69 | 70 | $dice = $this->dice->addRule('A', $rule); 71 | 72 | $a = $dice->create('A'); 73 | $this->assertInstanceOf('ExtendedB', $a->b); 74 | } 75 | 76 | public function testSubstitutionString() { 77 | $rule = []; 78 | 79 | $rule['substitutions']['B'] = [\Dice\Dice::INSTANCE => 'ExtendedB']; 80 | 81 | $dice = $this->dice->addRule('A', $rule); 82 | 83 | $a = $dice->create('A'); 84 | $this->assertInstanceOf('ExtendedB', $a->b); 85 | } 86 | 87 | 88 | public function testSubFromString() { 89 | $rule = [ 90 | 'substitutions' => ['Bar' => 'Baz'] 91 | ]; 92 | $dice = $this->dice->addRule('*', $rule); 93 | 94 | $obj = $dice->create('Foo'); 95 | 96 | $this->assertInstanceOf('Baz', $obj->bar); 97 | 98 | } 99 | 100 | public function testSubstitutionWithFuncCall() { 101 | $rule = []; 102 | 103 | $rule['substitutions']['Bar'] = [\Dice\Dice::INSTANCE => ['Foo2', 'bar']]; 104 | 105 | $dice = $this->dice->addRule('Foo', $rule); 106 | 107 | $a = $dice->create('Foo'); 108 | $this->assertInstanceOf('Baz', $a->bar); 109 | } 110 | } 111 | 112 | 113 | class Foo { 114 | public $bar; 115 | public function __construct(Bar $bar) { 116 | $this->bar = $bar; 117 | } 118 | } 119 | 120 | class Foo2 { 121 | public function bar() { 122 | return new Baz; 123 | } 124 | } 125 | 126 | interface Bar { 127 | 128 | } 129 | 130 | class Baz implements Bar { 131 | 132 | } 133 | -------------------------------------------------------------------------------- /tests/TestData/Basic.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | class NoConstructor { 8 | public $a = 'b'; 9 | } 10 | 11 | class CyclicA { 12 | public $b; 13 | 14 | public function __construct(CyclicB $b) { 15 | $this->b = $b; 16 | } 17 | } 18 | 19 | class CyclicB { 20 | public $a; 21 | 22 | public function __construct(CyclicA $a) { 23 | $this->a = $a; 24 | } 25 | } 26 | 27 | 28 | class A { 29 | public $b; 30 | 31 | public function __construct(B $b) { 32 | $this->b = $b; 33 | } 34 | } 35 | 36 | class B { 37 | public $c; 38 | 39 | public function __construct(C $c) { 40 | $this->c = $c; 41 | } 42 | } 43 | 44 | class ExtendedB extends B { 45 | 46 | } 47 | 48 | class C { 49 | public $d; 50 | public $e; 51 | 52 | public function __construct(D $d, E $e) { 53 | $this->d = $d; 54 | $this->e = $e; 55 | } 56 | } 57 | 58 | 59 | class D { 60 | 61 | } 62 | 63 | class E { 64 | public $f; 65 | public function __construct(F $f) { 66 | $this->f = $f; 67 | } 68 | } 69 | 70 | class F {} 71 | 72 | class RequiresConstructorArgsA { 73 | public $foo; 74 | public $bar; 75 | 76 | public function __construct($foo, $bar) { 77 | $this->foo = $foo; 78 | $this->bar = $bar; 79 | } 80 | } 81 | 82 | class MyObj { 83 | private $foo; 84 | 85 | public function setFoo($foo) { 86 | $this->foo = $foo; 87 | } 88 | 89 | public function getFoo() { 90 | return $this->foo; 91 | } 92 | } 93 | 94 | 95 | class MethodWithDefaultValue { 96 | public $a; 97 | public $foo; 98 | 99 | public function __construct(A $a, $foo = 'bar') { 100 | $this->a = $a; 101 | $this->foo = $foo; 102 | } 103 | } 104 | 105 | class MethodWithDefaultNull { 106 | public $a; 107 | public $b; 108 | public function __construct(A $a, B $b = null) { 109 | $this->a = $a; 110 | $this->b = $b; 111 | } 112 | } 113 | 114 | 115 | interface interfaceTest {} 116 | 117 | class InterfaceTestClass implements interfaceTest { 118 | 119 | } 120 | 121 | 122 | class ParentClass { 123 | } 124 | class Child extends ParentClass { 125 | } 126 | 127 | class OptionalInterface { 128 | public $obj; 129 | 130 | public function __construct(InterfaceTest $obj = null) { 131 | $this->obj = $obj; 132 | } 133 | } 134 | 135 | 136 | class ScalarTypeHint { 137 | public function __construct(string $a = null) { 138 | 139 | } 140 | } 141 | 142 | class CheckConstructorArgs { 143 | public $arg1; 144 | 145 | public function __construct($arg1) { 146 | $this->arg1 = $arg1; 147 | } 148 | } 149 | 150 | 151 | class someclass {} 152 | 153 | class someotherclass { 154 | public $obj; 155 | public function __construct(someclass $obj){ 156 | $this->obj = $obj; 157 | } 158 | } -------------------------------------------------------------------------------- /tests/TestData/Call.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | class TestCall { 8 | public $isCalled = false; 9 | 10 | public function callMe() { 11 | $this->isCalled = true; 12 | } 13 | } 14 | 15 | class TestCall2 { 16 | public $foo; 17 | public $bar; 18 | 19 | public function callMe($foo, $bar) { 20 | $this->foo = $foo; 21 | $this->bar = $bar; 22 | } 23 | } 24 | 25 | 26 | class TestCall3 { 27 | public $a; 28 | 29 | public function callMe(A $a) { 30 | $this->a = $a; 31 | 32 | return 'callMe called'; 33 | } 34 | } 35 | 36 | 37 | class TestCallImmutable { 38 | public $a; 39 | public $b; 40 | 41 | public function call1($a) { 42 | $new = clone $this; 43 | $new->a = $a; 44 | return $new; 45 | } 46 | 47 | public function call2($b) { 48 | $new = clone $this; 49 | $new->b = $b; 50 | return $new; 51 | } 52 | } 53 | 54 | class TestCallVariadic { 55 | public $data; 56 | 57 | public function callMe(...$data) { 58 | $this->data = $data; 59 | } 60 | } -------------------------------------------------------------------------------- /tests/TestData/ConstructParams.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | class MyDirectoryIterator extends DirectoryIterator { 8 | 9 | } 10 | 11 | 12 | class MyDirectoryIterator2 extends DirectoryIterator { 13 | public function __construct($f) { 14 | parent::__construct($f); 15 | } 16 | } 17 | 18 | class ParamRequiresArgs { 19 | public $a; 20 | 21 | public function __construct(D $d, RequiresConstructorArgsA $a) { 22 | $this->a = $a; 23 | } 24 | } 25 | 26 | 27 | 28 | class RequiresConstructorArgsB { 29 | public $a; 30 | public $foo; 31 | public $bar; 32 | 33 | public function __construct(A $a, $foo, $bar) { 34 | $this->a = $a; 35 | $this->foo = $foo; 36 | $this->bar = $bar; 37 | } 38 | } 39 | 40 | 41 | 42 | trait MyTrait { 43 | public function foo() {} 44 | } 45 | 46 | class MyDirectoryIteratorWithTrait extends DirectoryIterator { 47 | use MyTrait; 48 | } 49 | 50 | 51 | class NullScalar { 52 | public $string; 53 | 54 | public function __construct($string = null) { 55 | $this->string = $string; 56 | } 57 | } 58 | 59 | class NullScalarNested { 60 | public $nullScalar; 61 | 62 | public function __construct(NullScalar $nullScalar) { 63 | $this->nullScalar = $nullScalar; 64 | } 65 | } 66 | 67 | 68 | 69 | class NB {} 70 | 71 | class NC {} 72 | 73 | class MethodWithTwoDefaultNullC { 74 | public $a; 75 | public $b; 76 | public function __construct($a = null, NB $b = null) { 77 | $this->a = $a; 78 | $this->b = $b; 79 | } 80 | } 81 | 82 | class MethodWithTwoDefaultNullCC { 83 | public $a; 84 | public $b; 85 | public $c; 86 | public function __construct($a = null, NB $b = null, NC $c = null) { 87 | $this->a = $a; 88 | $this->b = $b; 89 | $this->c = $c; 90 | } 91 | } 92 | 93 | 94 | class NullableClassTypeHint { 95 | public $obj; 96 | 97 | public function __construct(?D $obj) { 98 | $this->obj = $obj; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /tests/TestData/CreateArgs.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | 8 | class ConsumeArgsTop { 9 | public $s; 10 | public $a; 11 | 12 | public function __construct(ConsumeArgsSub $a, $s) { 13 | $this->a = $a; 14 | $this->s = $s; 15 | } 16 | } 17 | class ConsumeArgsSub { 18 | public $s; 19 | 20 | public function __construct($s) { 21 | $this->s = $s; 22 | } 23 | } 24 | 25 | 26 | class A2 { 27 | public $b; 28 | public $c; 29 | public $foo; 30 | 31 | public function __construct(B $b, C $c, $foo) { 32 | $this->b = $b; 33 | $this->foo = $foo; 34 | $this->c = $c; 35 | } 36 | } 37 | 38 | 39 | class A3 { 40 | public $b; 41 | public $c; 42 | public $foo; 43 | 44 | public function __construct(C $c, $foo, B $b) { 45 | $this->b = $b; 46 | $this->foo = $foo; 47 | $this->c = $c; 48 | } 49 | } 50 | 51 | class A4 { 52 | public $m1; 53 | public $m2; 54 | public function __construct(M1 $m1, M2 $m2) { 55 | $this->m1 = $m1; 56 | $this->m2 = $m2; 57 | } 58 | } 59 | 60 | 61 | class BestMatch { 62 | public $a; 63 | public $string; 64 | public $b; 65 | 66 | public function __construct($string, A $a, B $b) { 67 | $this->a = $a; 68 | $this->string = $string; 69 | $this->b = $b; 70 | } 71 | } 72 | 73 | //From: https://github.com/TomBZombie/Dice/issues/62#issuecomment-112370319 74 | class ScalarConstructors { 75 | public $string; 76 | public $null; 77 | 78 | public function __construct($string, $null) { 79 | $this->string = $string; 80 | $this->null = $null; 81 | } 82 | } -------------------------------------------------------------------------------- /tests/TestData/NamedInstances.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | class Z { 8 | public $y1; 9 | public $y2; 10 | public function __construct(Y $y1, Y $y2) { 11 | $this->y1 = $y1; 12 | $this->y2 = $y2; 13 | } 14 | } 15 | 16 | class Y1 { 17 | public $y2; 18 | 19 | public function __construct(Y2 $y2) { 20 | $this->y2 = $y2; 21 | } 22 | } 23 | 24 | 25 | class Y2 { 26 | public $name; 27 | 28 | public function __construct($name) { 29 | $this->name = $name; 30 | } 31 | } 32 | 33 | class Y3 extends Y2 { 34 | 35 | } 36 | 37 | 38 | class Y { 39 | public $name; 40 | public function __construct($name) { 41 | $this->name = $name; 42 | } 43 | } 44 | 45 | 46 | class HasTwoSameDependencies { 47 | public $y2a; 48 | public $y2b; 49 | 50 | public function __construct(Y2 $y2a, Y2 $y2b) { 51 | $this->y2a = $y2a; 52 | $this->y2b = $y2b; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/TestData/Namespace.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | namespace Foo { 8 | 9 | class A { 10 | 11 | } 12 | 13 | class B { 14 | public $a; 15 | 16 | public function __construct(A $a) { 17 | $this->a = $a; 18 | } 19 | } 20 | 21 | class ExtendedA extends A { 22 | 23 | } 24 | 25 | 26 | class C { 27 | public $a; 28 | 29 | public function __construct(\Bar\A $a) { 30 | $this->a = $a; 31 | } 32 | } 33 | 34 | } 35 | 36 | namespace Bar { 37 | class A { 38 | 39 | } 40 | 41 | class B { 42 | 43 | } 44 | } -------------------------------------------------------------------------------- /tests/TestData/ShareInstances.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | class TestSharedInstancesTop { 8 | public $shared; 9 | public $share1; 10 | public $share2; 11 | 12 | public function __construct(Shared $shared, SharedInstanceTest1 $share1, SharedInstanceTest2 $share2) { 13 | $this->shared = $shared; 14 | $this->share1 = $share1; 15 | $this->share2 = $share2; 16 | } 17 | } 18 | 19 | 20 | 21 | 22 | class SharedInstanceTest1 { 23 | public $shared; 24 | 25 | public function __construct(Shared $shared) { 26 | $this->shared = $shared; 27 | } 28 | } 29 | 30 | 31 | class SharedInstanceTest2 { 32 | public $shared; 33 | 34 | public function __construct(Shared $shared) { 35 | $this->shared = $shared; 36 | } 37 | } 38 | 39 | 40 | 41 | class M1 { 42 | public $f; 43 | public function __construct(F $f) { 44 | $this->f = $f; 45 | } 46 | } 47 | 48 | class M2 { 49 | public $e; 50 | public function __construct(E $e) { 51 | $this->e = $e; 52 | } 53 | } 54 | 55 | class Foo77 { 56 | public $bar; 57 | 58 | public function __construct(Bar77 $bar) { 59 | $this->bar = $bar; 60 | } 61 | } 62 | 63 | class Bar77 { 64 | public $a; 65 | 66 | public function __construct($a) { 67 | $this->a = $a; 68 | } 69 | } 70 | 71 | 72 | class Baz77 { 73 | public static function create() { 74 | return new Bar77('Z'); 75 | } 76 | } 77 | 78 | class Shared { 79 | public $uniq; 80 | 81 | public function __construct() { 82 | $this->uniq = uniqid(); 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | | https:// r.je/dice.html * 5 | * @license http:// www.opensource.org/licenses/bsd-license.php BSD License * 6 | * @version 3.0 */ 7 | require_once 'Dice.php'; 8 | require_once 'tests/DiceTest.php'; 9 | --------------------------------------------------------------------------------