mirror of
https://github.com/yiisoft/yii2.git
synced 2026-02-20 00:32:19 +01:00
Fix #18285: Enhanced DI container to allow passing parameters by name in constructor
This commit is contained in:
committed by
GitHub
parent
840083724e
commit
23cfb38cea
@@ -358,8 +358,11 @@ $container->setSingleton('yii\db\Connection', [
|
||||
// "db" ранее зарегистрированный псевдоним
|
||||
$db = $container->get('db');
|
||||
|
||||
// эквивалентно: $engine = new \app\components\SearchEngine($apiKey, ['type' => 1]);
|
||||
$engine = $container->get('app\components\SearchEngine', [$apiKey], ['type' => 1]);
|
||||
// эквивалентно: $engine = new \app\components\SearchEngine($apiKey, $apiSecret, ['type' => 1]);
|
||||
$engine = $container->get('app\components\SearchEngine', [$apiKey, $apiSecret], ['type' => 1]);
|
||||
|
||||
// эквивалентно: $api = new \app\components\Api($host, $apiKey);
|
||||
$api = $container->get('app\components\Api', ['host' => $host, 'apiKey' => $apiKey]);
|
||||
```
|
||||
|
||||
За кулисами, контейнер внедрения зависимостей делает гораздо больше работы, чем просто создание нового объекта.
|
||||
|
||||
@@ -241,6 +241,9 @@ $db = $container->get('db');
|
||||
|
||||
// equivalent to: $engine = new \app\components\SearchEngine($apiKey, $apiSecret, ['type' => 1]);
|
||||
$engine = $container->get('app\components\SearchEngine', [$apiKey, $apiSecret], ['type' => 1]);
|
||||
|
||||
// equivalent to: $api = new \app\components\Api($host, $apiKey);
|
||||
$api = $container->get('app\components\Api', ['host' => $host, 'apiKey' => $apiKey]);
|
||||
```
|
||||
|
||||
Behind the scene, the DI container does much more work than just creating a new object.
|
||||
|
||||
@@ -16,8 +16,10 @@ Yii Framework 2 Change Log
|
||||
- Bug #18313: Fix multipart form data parse with double quotes (wsaid)
|
||||
- Bug #18317: Additional PHP 8 compatibility fixes (samdark, bizley)
|
||||
- Bug #16831: Fix console Table Widget does not render correctly in combination with ANSI formatting (issidorov, cebe)
|
||||
- Enh #18285: Enhanced DI container to allow passing parameters by name in constructor (vjik)
|
||||
- Enh #18351: Added option to change default timezone for parsing formats without time part in `yii\validators\DateValidator` (bizley)
|
||||
|
||||
|
||||
2.0.38 September 14, 2020
|
||||
-------------------------
|
||||
|
||||
|
||||
@@ -143,11 +143,14 @@ class Container extends Component
|
||||
* In this case, the constructor parameters and object configurations will be used
|
||||
* only if the class is instantiated the first time.
|
||||
*
|
||||
* @param string|Instance $class the class Instance, name or an alias name (e.g. `foo`) that was previously registered via [[set()]]
|
||||
* or [[setSingleton()]].
|
||||
* @param array $params a list of constructor parameter values. The parameters should be provided in the order
|
||||
* they appear in the constructor declaration. If you want to skip some parameters, you should index the remaining
|
||||
* ones with the integers that represent their positions in the constructor parameter list.
|
||||
* @param string|Instance $class the class Instance, name or an alias name (e.g. `foo`) that was previously
|
||||
* registered via [[set()]] or [[setSingleton()]].
|
||||
* @param array $params a list of constructor parameter values. Use one of two definitions:
|
||||
* - Parameters as name-value pairs, for example: `['posts' => PostRepository::class]`.
|
||||
* - Parameters in the order they appear in the constructor declaration. If you want to skip some parameters,
|
||||
* you should index the remaining ones with the integers that represent their positions in the constructor
|
||||
* parameter list.
|
||||
* Dependencies indexed by name and by position in the same array are not allowed.
|
||||
* @param array $config a list of name-value pairs that will be used to initialize the object properties.
|
||||
* @return object an instance of the requested class.
|
||||
* @throws InvalidConfigException if the class cannot be recognized or correspond to an invalid definition
|
||||
@@ -379,15 +382,23 @@ class Container extends Component
|
||||
/* @var $reflection ReflectionClass */
|
||||
list($reflection, $dependencies) = $this->getDependencies($class);
|
||||
|
||||
$addDependencies = [];
|
||||
if (isset($config['__construct()'])) {
|
||||
foreach ($config['__construct()'] as $index => $param) {
|
||||
$dependencies[$index] = $param;
|
||||
}
|
||||
$addDependencies = $config['__construct()'];
|
||||
unset($config['__construct()']);
|
||||
}
|
||||
|
||||
foreach ($params as $index => $param) {
|
||||
$dependencies[$index] = $param;
|
||||
$addDependencies[$index] = $param;
|
||||
}
|
||||
|
||||
$this->validateDependencies($addDependencies);
|
||||
|
||||
if ($addDependencies && is_int(key($addDependencies))) {
|
||||
$dependencies = array_values($dependencies);
|
||||
$dependencies = $this->mergeDependencies($dependencies, $addDependencies);
|
||||
} else {
|
||||
$dependencies = $this->mergeDependencies($dependencies, $addDependencies);
|
||||
$dependencies = array_values($dependencies);
|
||||
}
|
||||
|
||||
$dependencies = $this->resolveDependencies($dependencies, $reflection);
|
||||
@@ -414,6 +425,47 @@ class Container extends Component
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $a
|
||||
* @param array $b
|
||||
* @return array
|
||||
*/
|
||||
private function mergeDependencies($a, $b)
|
||||
{
|
||||
foreach ($b as $index => $dependency) {
|
||||
$a[$index] = $dependency;
|
||||
}
|
||||
return $a;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $parameters
|
||||
* @throws InvalidConfigException
|
||||
*/
|
||||
private function validateDependencies($parameters)
|
||||
{
|
||||
$hasStringParameter = false;
|
||||
$hasIntParameter = false;
|
||||
foreach ($parameters as $index => $parameter) {
|
||||
if (is_string($index)) {
|
||||
$hasStringParameter = true;
|
||||
if ($hasIntParameter) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$hasIntParameter = true;
|
||||
if ($hasStringParameter) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($hasIntParameter && $hasStringParameter) {
|
||||
throw new InvalidConfigException(
|
||||
'Dependencies indexed by name and by position in the same array are not allowed.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the user-specified constructor parameters with the ones registered via [[set()]].
|
||||
* @param string $class class name, interface name or alias name
|
||||
@@ -463,7 +515,7 @@ class Container extends Component
|
||||
}
|
||||
|
||||
if ($param->isDefaultValueAvailable()) {
|
||||
$dependencies[] = $param->getDefaultValue();
|
||||
$dependencies[$param->getName()] = $param->getDefaultValue();
|
||||
} else {
|
||||
if (PHP_VERSION_ID >= 80000) {
|
||||
$c = $param->getType();
|
||||
@@ -472,7 +524,7 @@ class Container extends Component
|
||||
$c = $param->getClass();
|
||||
$isClass = $c !== null;
|
||||
}
|
||||
$dependencies[] = Instance::of($isClass ? $c->getName() : null);
|
||||
$dependencies[$param->getName()] = Instance::of($isClass ? $c->getName() : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use yiiunit\data\ar\Order;
|
||||
use yiiunit\data\ar\Type;
|
||||
use yiiunit\framework\di\stubs\Bar;
|
||||
use yiiunit\framework\di\stubs\BarSetter;
|
||||
use yiiunit\framework\di\stubs\Car;
|
||||
use yiiunit\framework\di\stubs\Corge;
|
||||
use yiiunit\framework\di\stubs\Foo;
|
||||
use yiiunit\framework\di\stubs\FooProperty;
|
||||
@@ -172,7 +173,7 @@ class ContainerTest extends TestCase
|
||||
|
||||
|
||||
$myFunc = function ($a, NumberValidator $b, $c = 'default') {
|
||||
return[$a, \get_class($b), $c];
|
||||
return [$a, \get_class($b), $c];
|
||||
};
|
||||
$result = Yii::$container->invoke($myFunc, ['a']);
|
||||
$this->assertEquals(['a', 'yii\validators\NumberValidator', 'default'], $result);
|
||||
@@ -262,7 +263,8 @@ class ContainerTest extends TestCase
|
||||
'qux.using.closure' => function () {
|
||||
return new Qux();
|
||||
},
|
||||
'rollbar', 'baibaratsky\yii\rollbar\Rollbar'
|
||||
'rollbar',
|
||||
'baibaratsky\yii\rollbar\Rollbar'
|
||||
]);
|
||||
$container->setDefinitions([]);
|
||||
|
||||
@@ -278,8 +280,7 @@ class ContainerTest extends TestCase
|
||||
try {
|
||||
$container->get('rollbar');
|
||||
$this->fail('InvalidConfigException was not thrown');
|
||||
} catch(\Exception $e)
|
||||
{
|
||||
} catch (\Exception $e) {
|
||||
$this->assertInstanceOf('yii\base\InvalidConfigException', $e);
|
||||
}
|
||||
}
|
||||
@@ -527,4 +528,30 @@ class ContainerTest extends TestCase
|
||||
Yii::$container->set('setLater', new Qux());
|
||||
Yii::$container->get('test');
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/yiisoft/yii2/issues/18284
|
||||
*/
|
||||
public function testNamedConstructorParameters()
|
||||
{
|
||||
$test = (new Container())->get(Car::className(), [
|
||||
'name' => 'Hello',
|
||||
'color' => 'red',
|
||||
]);
|
||||
$this->assertSame('Hello', $test->name);
|
||||
$this->assertSame('red', $test->color);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/yiisoft/yii2/issues/18284
|
||||
*/
|
||||
public function testInvalidConstructorParameters()
|
||||
{
|
||||
$this->expectException('yii\base\InvalidConfigException');
|
||||
$this->expectExceptionMessage('Dependencies indexed by name and by position in the same array are not allowed.');
|
||||
(new Container())->get(Car::className(), [
|
||||
'color' => 'red',
|
||||
'Hello',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
17
tests/framework/di/stubs/Car.php
Normal file
17
tests/framework/di/stubs/Car.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace yiiunit\framework\di\stubs;
|
||||
|
||||
use yii\base\BaseObject;
|
||||
|
||||
class Car extends BaseObject
|
||||
{
|
||||
public $color;
|
||||
public $name;
|
||||
|
||||
public function __construct($color, $name)
|
||||
{
|
||||
$this->color = $color;
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user