@ -8,8 +8,10 @@
namespace yii\di;
namespace yii\di;
use ReflectionClass;
use ReflectionClass;
use Yii;
use yii\base\Component;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
/**
/**
* Container implements a [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection) container.
* Container implements a [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection) container.
@ -456,50 +458,93 @@ class Container extends Component
}
}
/**
/**
* Invoke callback with resolved dependecies parameters.
* Invoke a callback with resolving dependencies in parameters.
*
* This methods allows invoking a callback and let type hinted parameter names to be
* resolved as objects of the Container. It additionally allow calling function using named parameters.
*
* For example, the following callback may be invoked using the Container to resolve the formatter dependency:
*
* ```php
* $formatString = function($string, \yii\i18n\Formatter $formatter) {
* // ...
* }
* Yii::$container->invoke($formatString, ['string' => 'Hello World!']);
* ```
*
* This will pass the string `'Hello World!'` as the first param, and a formatter instance created
* by the DI container as the second param to the callable.
*
* @param callable $callback callable to be invoked.
* @param callable $callback callable to be invoked.
* @param array $params callback paramater.
* @param array $params The array of parameters for the function.
* This can be either a list of parameters, or an associative array representing named function parameters.
* @return mixed the callback return value.
* @return mixed the callback return value.
* @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled.
* @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled.
* @since 2.0.7
* @since 2.0.7
*/
*/
public function invoke($callback, $params = [])
public function invoke(callable $callback, $params = [])
{
{
if (is_callable($callback)) {
if (is_callable($callback)) {
if (is_array($callback)) {
return call_user_func_array($callback, $this->resolveCallableDependencies($callback, $params));
$reflection = new \ReflectionMethod($callback[0], $callback[1]);
} else {
} else {
return call_user_func_array($callback, $params);
$reflection = new \ReflectionFunction($callback);
}
}
}
$args = [];
/**
foreach ($reflection->getParameters() as $param) {
* Resolve dependencies for a function.
$name = $param->getName();
*
if (($class = $param->getClass()) !== null) {
* This method can be used to implement similar functionality as provided by [[invoke()]] in other
$className = $class->getName();
* components.
if (isset($params[0]) & & $params[0] instanceof $className) {
*
$args[] = array_shift($params);
* @param callable $callback callable to be invoked.
} elseif (\Yii::$app->has($name) & & ($obj = \Yii::$app->get($name)) instanceof $className) {
* @param array $params The array of parameters for the function, can be either numeric or associative.
$args[] = $obj;
* @return array The resolved dependencies.
} else {
* @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled.
$args[] = $this->get($className);
* @since 2.0.7
}
*/
} elseif (count($params)) {
public function resolveCallableDependencies(callable $callback, $params = [])
{
if (is_array($callback)) {
$reflection = new \ReflectionMethod($callback[0], $callback[1]);
} else {
$reflection = new \ReflectionFunction($callback);
}
$args = [];
$associative = ArrayHelper::isAssociative($params);
foreach ($reflection->getParameters() as $param) {
$name = $param->getName();
if (($class = $param->getClass()) !== null) {
$className = $class->getName();
if ($associative & & isset($params[$name]) & & $params[$name] instanceof $className) {
$args[] = $params[$name];
unset($params[$name]);
} elseif (!$associative & & isset($params[0]) & & $params[0] instanceof $className) {
$args[] = array_shift($params);
$args[] = array_shift($params);
} elseif ($param->isDefaultValueAvailable()) {
} elseif (Yii::$app->has($name) & & ($obj = Yii::$app->get($name)) instanceof $className) {
$args[] = $param->getDefaultValue();
$args[] = $obj;
} elseif (!$param->isOptional()) {
} else {
$funcName = $reflection->getName();
$args[] = $this->get($className);
throw new InvalidConfigException("Missing required parameter \"$name\" when calling \"$funcName\".");
}
}
} elseif ($associative & & isset($params[$name])) {
$args[] = $params[$name];
unset($params[$name]);
} elseif (!$associative & & count($params)) {
$args[] = array_shift($params);
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
} elseif (!$param->isOptional()) {
$funcName = $reflection->getName();
throw new InvalidConfigException("Missing required parameter \"$name\" when calling \"$funcName\".");
}
}
}
foreach ($params as $value) {
foreach ($params as $value) {
$args[] = $value;
$args[] = $value;
}
return call_user_func_array($callback, $args);
} else {
return call_user_func_array($callback, $params);
}
}
return $args;
}
}
}
}