mirror of
https://github.com/yiisoft/yii2.git
synced 2025-08-26 14:26:54 +08:00
Several improvements to DI container
- Refactored invoke into separate resolve method (reusability) - Added support for associative params array - Added PHPUnit group doc to DI tests. - Improved the tests for better coverage, and fixed a bug discovered by the new tests. close #10811
This commit is contained in:

committed by
Carsten Brandt

parent
3d719d057e
commit
8ea4c660da
@ -94,7 +94,7 @@ Yii Framework 2 Change Log
|
||||
- Enh #9733: Added Unprocessable Entity HTTP Exception (janfrs)
|
||||
- Enh #9762: Added `JsonResponseFormatter::$encodeOptions` and `::$prettyPrint` for better JSON output formatting (cebe)
|
||||
- Enh #9783: jQuery inputmask dependency updated to `~3.2.2` (samdark)
|
||||
- Enh #9785: Added ability to invoke callback with dependency resolution to DI container (mdmunir)
|
||||
- Enh #9785: Added ability to invoke callback with dependency resolution to DI container (mdmunir, SamMousa)
|
||||
- Enh #9869: Allow path alias for SQLite database files in DSN config (ASlatius)
|
||||
- Enh #9901: Default `Cache.SerializerPermissions` configuration option for `HTMLPurifier` is set to `0775` (klimov-paul)
|
||||
- Enh #10056: Allowed any callable to be passed to `ActionColumn::$urlCreator` (freezy-sk)
|
||||
|
@ -8,8 +8,10 @@
|
||||
namespace yii\di;
|
||||
|
||||
use ReflectionClass;
|
||||
use Yii;
|
||||
use yii\base\Component;
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
/**
|
||||
* 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 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.
|
||||
* @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled.
|
||||
* @since 2.0.7
|
||||
*/
|
||||
public function invoke($callback, $params = [])
|
||||
public function invoke(callable $callback, $params = [])
|
||||
{
|
||||
if (is_callable($callback)) {
|
||||
if (is_array($callback)) {
|
||||
$reflection = new \ReflectionMethod($callback[0], $callback[1]);
|
||||
} else {
|
||||
$reflection = new \ReflectionFunction($callback);
|
||||
}
|
||||
|
||||
$args = [];
|
||||
foreach ($reflection->getParameters() as $param) {
|
||||
$name = $param->getName();
|
||||
if (($class = $param->getClass()) !== null) {
|
||||
$className = $class->getName();
|
||||
if (isset($params[0]) && $params[0] instanceof $className) {
|
||||
$args[] = array_shift($params);
|
||||
} elseif (\Yii::$app->has($name) && ($obj = \Yii::$app->get($name)) instanceof $className) {
|
||||
$args[] = $obj;
|
||||
} else {
|
||||
$args[] = $this->get($className);
|
||||
}
|
||||
} elseif (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) {
|
||||
$args[] = $value;
|
||||
}
|
||||
return call_user_func_array($callback, $args);
|
||||
return call_user_func_array($callback, $this->resolveCallableDependencies($callback, $params));
|
||||
} else {
|
||||
return call_user_func_array($callback, $params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve dependencies for a function.
|
||||
*
|
||||
* This method can be used to implement similar functionality as provided by [[invoke()]] in other
|
||||
* components.
|
||||
*
|
||||
* @param callable $callback callable to be invoked.
|
||||
* @param array $params The array of parameters for the function, can be either numeric or associative.
|
||||
* @return array The resolved dependencies.
|
||||
* @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled.
|
||||
* @since 2.0.7
|
||||
*/
|
||||
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);
|
||||
} elseif (Yii::$app->has($name) && ($obj = Yii::$app->get($name)) instanceof $className) {
|
||||
$args[] = $obj;
|
||||
} else {
|
||||
$args[] = $this->get($className);
|
||||
}
|
||||
} 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) {
|
||||
$args[] = $value;
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ use yii\validators\NumberValidator;
|
||||
/**
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
* @group di
|
||||
*/
|
||||
class ContainerTest extends TestCase
|
||||
{
|
||||
@ -169,4 +170,47 @@ class ContainerTest extends TestCase
|
||||
$this->assertEquals($response, Yii::$app->response);
|
||||
|
||||
}
|
||||
|
||||
public function testAssociativeInvoke()
|
||||
{
|
||||
$this->mockApplication([
|
||||
'components' => [
|
||||
'qux' => [
|
||||
'class' => 'yiiunit\framework\di\stubs\Qux',
|
||||
'a' => 'belongApp',
|
||||
],
|
||||
'qux2' => [
|
||||
'class' => 'yiiunit\framework\di\stubs\Qux',
|
||||
'a' => 'belongAppQux2',
|
||||
],
|
||||
]
|
||||
]);
|
||||
$closure = function($a, $x = 5, $b) {
|
||||
return $a > $b;
|
||||
};
|
||||
$this->assertFalse(Yii::$container->invoke($closure, ['b' => 5, 'a' => 1]));
|
||||
$this->assertTrue(Yii::$container->invoke($closure, ['b' => 1, 'a' => 5]));
|
||||
}
|
||||
|
||||
public function testResolveCallableDependencies()
|
||||
{
|
||||
$this->mockApplication([
|
||||
'components' => [
|
||||
'qux' => [
|
||||
'class' => 'yiiunit\framework\di\stubs\Qux',
|
||||
'a' => 'belongApp',
|
||||
],
|
||||
'qux2' => [
|
||||
'class' => 'yiiunit\framework\di\stubs\Qux',
|
||||
'a' => 'belongAppQux2',
|
||||
],
|
||||
]
|
||||
]);
|
||||
$closure = function($a, $b) {
|
||||
return $a > $b;
|
||||
};
|
||||
$this->assertEquals([1, 5], Yii::$container->resolveCallableDependencies($closure, ['b' => 5, 'a' => 1]));
|
||||
$this->assertEquals([1, 5], Yii::$container->resolveCallableDependencies($closure, ['a' => 1, 'b' => 5]));
|
||||
$this->assertEquals([1, 5], Yii::$container->resolveCallableDependencies($closure, [1, 5]));
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ use yiiunit\TestCase;
|
||||
/**
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
* @group di
|
||||
*/
|
||||
class InstanceTest extends TestCase
|
||||
{
|
||||
|
@ -28,6 +28,7 @@ class TestClass extends Object
|
||||
/**
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
* @group di
|
||||
*/
|
||||
class ServiceLocatorTest extends TestCase
|
||||
{
|
||||
|
Reference in New Issue
Block a user