diff --git a/src/DI/Compiler.php b/src/DI/Compiler.php index 35e12ed7d..614cd4519 100644 --- a/src/DI/Compiler.php +++ b/src/DI/Compiler.php @@ -156,7 +156,7 @@ public function getConfig(): array public function setDynamicParameterNames(array $names) { assert($this->extensions[self::Parameters] instanceof Extensions\ParametersExtension); - $this->extensions[self::Parameters]->dynamicParams = $names; + $this->extensions[self::Parameters]->dynamicParams = array_flip($names); return $this; } diff --git a/src/DI/Container.php b/src/DI/Container.php index 584c049c9..dfc00749a 100644 --- a/src/DI/Container.php +++ b/src/DI/Container.php @@ -60,6 +60,21 @@ public function getParameters(): array } + public function getParameter($key) + { + if (!array_key_exists($key, $this->parameters)) { + $this->parameters[$key] = $this->getDynamicParameter($key); + } + return $this->parameters[$key]; + } + + + protected function getDynamicParameter($key) + { + throw new Nette\InvalidStateException(sprintf('Dynamic parameter %s not found.', $key)); + } + + /** * Adds the service to the container. * @param object $service service or its factory diff --git a/src/DI/Extensions/ParametersExtension.php b/src/DI/Extensions/ParametersExtension.php index a8928bd5f..e7d5b16d9 100644 --- a/src/DI/Extensions/ParametersExtension.php +++ b/src/DI/Extensions/ParametersExtension.php @@ -18,7 +18,7 @@ */ final class ParametersExtension extends Nette\DI\CompilerExtension { - /** @var string[] */ + /** @var array */ public $dynamicParams = []; /** @var string[][] */ @@ -38,8 +38,6 @@ public function loadConfiguration() { $builder = $this->getContainerBuilder(); $params = $this->config; - $resolver = new Nette\DI\Resolver($builder); - $generator = new Nette\DI\PhpGenerator($builder); // expressions are automatically treated as dynamic params foreach ($params as $key => $value) { @@ -51,10 +49,8 @@ public function loadConfiguration() }); } - foreach ($this->dynamicParams as $key) { - $params[$key] = array_key_exists($key, $params) - ? new DynamicParameter($generator->formatPhp('($this->parameters[?] \?\? ?)', $resolver->completeArguments(Nette\DI\Helpers::filterArguments([$key, $params[$key]])))) - : new DynamicParameter((new Nette\PhpGenerator\Dumper)->format('$this->parameters[?]', $key)); + foreach ($this->dynamicParams as $key => $foo) { + $params[$key] = new DynamicParameter((new Nette\PhpGenerator\Dumper)->format('$this->getParameter(?)', $key)); } $builder->parameters = Nette\DI\Helpers::expand($params, $params, true); @@ -68,18 +64,48 @@ public function loadConfiguration() public function afterCompile(Nette\PhpGenerator\ClassType $class) { - $params = $this->getContainerBuilder()->parameters; - array_walk_recursive($params, function (&$val): void { - if ($val instanceof Nette\DI\Definitions\Statement || $val instanceof DynamicParameter) { - $val = null; - } - }); + $builder = $this->getContainerBuilder(); + + // static parameters + $staticParams = $builder->parameters; + foreach ($staticParams as $key => $value) { + $tmp = [$value]; + array_walk_recursive($tmp, function ($val) use ($key, &$staticParams): void { + if ($val instanceof DynamicParameter) { + $this->dynamicParams[$key] = null; + unset($staticParams[$key]); + } + }); + } $cnstr = $class->getMethod('__construct'); - $cnstr->addBody('$this->parameters += ?;', [$params]); + $cnstr->addBody('$this->parameters += ?;', [$staticParams]); + + if (!$this->dynamicParams) { + return; + } + + // dynamic parameters + $resolver = new Nette\DI\Resolver($builder); + $generator = new Nette\DI\PhpGenerator($builder); + + $getter = $class->addMethod('getDynamicParameter') + ->setProtected(); + $getter->addParameter('key'); + $body = 'switch (true) {'; + foreach ($this->dynamicParams as $key => $foo) { + $value = Nette\DI\Helpers::expand($this->config[$key] ?? null, $builder->parameters); + $value = $resolver->completeArguments(Nette\DI\Helpers::filterArguments([$value])); + $body .= "\n\t" . 'case $key === ' . var_export($key, true) . ': return ' . $generator->formatPhp('?', $value) . ';'; + } + $body .= "\n\t" . 'default: parent::getDynamicParameter($key);' . "\n};"; + $getter->addBody($body); + + // dynamic parameters validation + $initialize = $class->getMethod('initialize'); foreach ($this->dynamicValidators as [$param, $expected]) { if (!$param instanceof Nette\DI\Definitions\Statement) { - $cnstr->addBody('Nette\Utils\Validators::assert(?, ?, ?);', [$param, $expected, 'dynamic parameter']); + $initialize->addBody('Nette\Utils\Validators::assert(?, ?, ?);', [$param, $expected, 'dynamic parameter']); } } } diff --git a/tests/DI/Compiler.dynamicParameters.phpt b/tests/DI/Compiler.dynamicParameters.phpt index 5dfb8895b..e374c6521 100644 --- a/tests/DI/Compiler.dynamicParameters.phpt +++ b/tests/DI/Compiler.dynamicParameters.phpt @@ -126,18 +126,3 @@ test('', function () { '); Assert::same('hello', $container->getService('one')->arg); }); - - -test('', function () { - $compiler = new DI\Compiler; - $compiler->setDynamicParameterNames(['dynamic']); - Assert::exception(function () use ($compiler) { - createContainer($compiler, ' - parameters: - dynamic: @one - - services: - one: Service - '); - }, Nette\DI\ServiceCreationException::class, "Reference to missing service 'one'."); -}); diff --git a/tests/DI/Compiler.dynamicParameters.validator.phpt b/tests/DI/Compiler.dynamicParameters.validator.phpt index 0d6cb4eb7..44f0c3834 100644 --- a/tests/DI/Compiler.dynamicParameters.validator.phpt +++ b/tests/DI/Compiler.dynamicParameters.validator.phpt @@ -28,11 +28,12 @@ test('', function () { $compiler->addExtension('foo', new FooExtension); $compiler->setDynamicParameterNames(['dynamic']); Assert::exception(function () use ($compiler) { - createContainer($compiler, ' + $container = createContainer($compiler, ' foo: key: string: %dynamic% ', ['dynamic' => 123]); + $container->initialize(); }, Nette\Utils\AssertionException::class, 'The dynamic parameter expects to be string, int 123 given.'); }); @@ -42,11 +43,12 @@ test('', function () { $compiler->addExtension('foo', new FooExtension); $compiler->setDynamicParameterNames(['dynamic']); Assert::exception(function () use ($compiler) { - createContainer($compiler, ' + $container = createContainer($compiler, ' foo: key: string: %dynamic% ', ['dynamic' => null]); + $container->initialize(); }, Nette\Utils\AssertionException::class, 'The dynamic parameter expects to be string, null given.'); }); @@ -56,11 +58,12 @@ test('', function () { $compiler->addExtension('foo', new FooExtension); $compiler->setDynamicParameterNames(['dynamic']); Assert::exception(function () use ($compiler) { - createContainer($compiler, ' + $container = createContainer($compiler, ' foo: key: string: %dynamic.sub% ', ['dynamic' => ['sub' => 123]]); + $container->initialize(); }, Nette\Utils\AssertionException::class, 'The dynamic parameter expects to be string, int 123 given.'); }); @@ -70,11 +73,12 @@ test('', function () { $compiler->addExtension('foo', new FooExtension); $compiler->setDynamicParameterNames(['dynamic']); Assert::noError(function () use ($compiler) { - createContainer($compiler, ' + $container = createContainer($compiler, ' foo: key: intnull: %dynamic% ', ['dynamic' => 123]); + $container->initialize(); }); }); @@ -84,7 +88,7 @@ test('', function () { $compiler->addExtension('foo', new FooExtension); $compiler->setDynamicParameterNames(['dynamic']); Assert::noError(function () use ($compiler) { - createContainer($compiler, ' + $container = createContainer($compiler, ' foo: key: intnull: %dynamic% diff --git a/tests/DI/Compiler.parameters.phpt b/tests/DI/Compiler.parameters.phpt index ef81cbb59..e25b1d098 100644 --- a/tests/DI/Compiler.parameters.phpt +++ b/tests/DI/Compiler.parameters.phpt @@ -38,7 +38,7 @@ test('', function () { services: one: Service(%bar%) '); - Assert::null($container->parameters['bar']); + Assert::same('a', $container->getParameter('bar')); Assert::same('a', $container->getService('one')->arg); }); @@ -66,7 +66,7 @@ test('', function () { services: one: Service(%bar%) '); - Assert::null($container->parameters['bar']); + Assert::same('Service::method hello', $container->getParameter('bar')); Assert::same('Service::method hello', $container->getService('one')->arg); }); @@ -81,7 +81,7 @@ test('', function () { one: Service(%bar%) two: Service(two) '); - Assert::same('@two', $container->parameters['bar']); // not resolved + Assert::same('@two', $container->getParameter('bar')); // not resolved Assert::same($container->getService('two'), $container->getService('one')->arg); }); @@ -96,7 +96,7 @@ test('', function () { one: Service(%bar%) two: Service(two) '); - Assert::null($container->parameters['bar']); + Assert::type(Service::class, $container->getParameter('bar')); Assert::same($container->getService('two'), $container->getService('one')->arg->arg); }); @@ -111,6 +111,12 @@ test('', function () { one: Service(%bar%) two: Service(two) '); - Assert::null($container->parameters['bar']); - Assert::same([$container->getService('two')], $container->getService('one')->arg); + + Assert::exception( + function () use ($container) { + $container->getParameter('bar'); + }, + Nette\InvalidStateException::class, + 'Circular reference detected for services: one.' + ); }); diff --git a/tests/DI/DIExtension.exportParameters.phpt b/tests/DI/DIExtension.exportParameters.phpt index fbe770ab5..ba6b10357 100644 --- a/tests/DI/DIExtension.exportParameters.phpt +++ b/tests/DI/DIExtension.exportParameters.phpt @@ -59,7 +59,8 @@ test('', function () { parameters: true ', ['dynamic' => 123]); - Assert::same(['dynamic' => 123, 'key' => null], $container->parameters); + Assert::same(['dynamic' => 123], $container->parameters); + Assert::same(123, $container->getParameter('key')); });