private function getClosure($name, array $rule)
{
// Reflect the class and constructor, this should only ever be done once per class and get cached
$class = new \ReflectionClass(isset($rule['instanceOf']) ? $rule['instanceOf'] : $name);
$constructor = $class->getConstructor();
// Create parameter generating function in order to cache reflection on the parameters. This way $reflect->getParameters() only ever gets called once
$params = $constructor ? $this->getParams($constructor, $rule) : null;
// Get a closure based on the type of object being created: Shared, normal or constructorless
if (!empty($rule['shared'])) {
$closure = function (array $args, array $share) use($class, $name, $constructor, $params) {
// Shared instance: create the class without calling the constructor (and write to \$name and $name, see issue #68)
$this->instances[$name] = $this->instances[ltrim($name, '\\')] = $class->newInstanceWithoutConstructor();
// Now call this constructor after constructing all the dependencies. This avoids problems with cyclic references (issue #7)
if ($constructor) {
$constructor->invokeArgs($this->instances[$name], $params($args, $share));
}
return $this->instances[$name];
};
} else {
if ($params) {
$closure = function (array $args, array $share) use($class, $params) {
// This class has depenencies, call the $params closure to generate them based on $args and $share
return new $class->name(...$params($args, $share));
};
} else {
$closure = function () use($class) {
// No constructor arguments, just instantiate the class
return new $class->name();
};
}
}
// If there are shared instances, create them and merge them with shared instances higher up the object graph
if (isset($rule['shareInstances'])) {
$closure = function (array $args, array $share) use($closure, $rule) {
return $closure($args, array_merge($args, $share, array_map([$this, 'create'], $rule['shareInstances'])));
};
}
// When $rule['call'] is set, wrap the closure in another closure which will call the required methods after constructing the object
// By putting this in a closure, the loop is never executed unless call is actually set
return isset($rule['call']) ? function (array $args, array $share) use($closure, $class, $rule) {
// Construct the object using the original closure
$object = $closure($args, $share);
foreach ($rule['call'] as $call) {
// Generate the method arguments using getParams() and call the returned closure (in php7 will be ()() rather than __invoke)
$params = $this->getParams($class->getMethod($call[0]), ['shareInstances' => isset($rule['shareInstances']) ? $rule['shareInstances'] : []])->__invoke($this->expand(isset($call[1]) ? $call[1] : []));
$object->{$call[0]}(...$params);
}
return $object;
} : $closure;
}