Browse Source

Fix #18285: Enhanced DI container to allow passing parameters by name in constructor

tags/2.0.39
Sergei Predvoditelev 4 years ago committed by GitHub
parent
commit
23cfb38cea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      docs/guide-ru/concept-di-container.md
  2. 3
      docs/guide/concept-di-container.md
  3. 2
      framework/CHANGELOG.md
  4. 76
      framework/di/Container.php
  5. 35
      tests/framework/di/ContainerTest.php
  6. 17
      tests/framework/di/stubs/Car.php

7
docs/guide-ru/concept-di-container.md

@ -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]);
```
За кулисами, контейнер внедрения зависимостей делает гораздо больше работы, чем просто создание нового объекта.

3
docs/guide/concept-di-container.md

@ -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.

2
framework/CHANGELOG.md

@ -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
-------------------------

76
framework/di/Container.php

@ -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);
@ -415,6 +426,47 @@ class Container extends Component
}
/**
* @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
* @param array $params the constructor parameters
@ -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);
}
}
}

35
tests/framework/di/ContainerTest.php

@ -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

@ -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;
}
}
Loading…
Cancel
Save