Skip to content

Commit

Permalink
ParametersExtension, Container: redesigned way of handling dynamic pa…
Browse files Browse the repository at this point in the history
…rameters via getParameter() [Closes #291][Closes #288]

- parameters with expressions are automatically treated as dynamic
  • Loading branch information
dg committed Oct 15, 2023
1 parent bf2a6e8 commit 5d29ef2
Show file tree
Hide file tree
Showing 14 changed files with 180 additions and 82 deletions.
3 changes: 3 additions & 0 deletions src/Bridges/DITracy/ContainerPanel.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ public function getPanel(): string
$file = $rc->getFileName();
$instances = (function () { return $this->instances; })->bindTo($this->container, Container::class)();
$wiring = (function () { return $this->wiring; })->bindTo($this->container, $this->container)();
$parameters = $rc->getMethod('getStaticParameters')->getDeclaringClass()->getName() === Container::class
? null
: $container->getParameters();
require __DIR__ . '/templates/ContainerPanel.panel.phtml';
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/Bridges/DITracy/templates/ContainerPanel.panel.phtml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ use Tracy\Helpers;
<h2>Parameters</h2>

<div class="nette-ContainerPanel-parameters">
<?= $container->parameters ? Dumper::toHtml($container->parameters) : "<i>disabled via 'di › export › parameters'</i>" ?>
<?= $parameters === null ? "<i>disabled via 'di › export › parameters'</i>" : Dumper::toHtml($parameters) ?>
</div>
</div>
</div>
28 changes: 26 additions & 2 deletions src/DI/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ class Container
{
use Nette\SmartObject;

/** @var array user parameters */
/**
* @var mixed[]
* @deprecated use Container::getParameter() or getParameters()
*/
public $parameters = [];

/** @var string[] services name => type (complete list of available services) */
Expand All @@ -46,7 +49,7 @@ class Container

public function __construct(array $params = [])
{
$this->parameters = $params;
$this->parameters = $params + $this->getStaticParameters();
$this->methods = array_flip(array_filter(
get_class_methods($this),
function ($s) { return preg_match('#^createService.#', $s); }
Expand All @@ -60,6 +63,27 @@ 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 getStaticParameters(): array
{
return [];
}


protected function getDynamicParameter($key)
{
throw new Nette\InvalidStateException(sprintf("Parameter '%s' not found. Check if 'di › export › parameters' is enabled.", $key));
}


/**
* Adds the service to the container.
* @param object $service service or its factory
Expand Down
17 changes: 9 additions & 8 deletions src/DI/Extensions/DIExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,13 @@ public function loadConfiguration()
}


public function beforeCompile()
{
if (!$this->config->export->parameters) {
$this->getContainerBuilder()->parameters = [];
}
}


public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
if ($this->config->parentClass) {
$class->setExtends($this->config->parentClass);
}

$this->restrictParameters($class);
$this->restrictTags($class);
$this->restrictTypes($class);

Expand All @@ -95,6 +88,14 @@ public function afterCompile(Nette\PhpGenerator\ClassType $class)
}


private function restrictParameters(Nette\PhpGenerator\ClassType $class): void
{
if (!$this->config->export->parameters) {
$class->removeMethod('getStaticParameters');
}
}


private function restrictTags(Nette\PhpGenerator\ClassType $class): void
{
$option = $this->config->export->tags;
Expand Down
62 changes: 41 additions & 21 deletions src/DI/Extensions/ParametersExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
namespace Nette\DI\Extensions;

use Nette;
use Nette\DI\Container;
use Nette\DI\DynamicParameter;
use Nette\PhpGenerator\Method;


/**
Expand All @@ -37,15 +39,7 @@ public function __construct(array &$compilerConfig)
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
$params = $this->config;
$resolver = new Nette\DI\Resolver($builder);
$generator = new Nette\DI\PhpGenerator($builder);

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));
}
$params = array_fill_keys($this->dynamicParams, new DynamicParameter('')) + $this->config;

$builder->parameters = Nette\DI\Helpers::expand($params, $params, true);

Expand All @@ -58,21 +52,47 @@ public function loadConfiguration()

public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$parameters = $this->getContainerBuilder()->parameters;
array_walk_recursive($parameters, function (&$val): void {
if ($val instanceof Nette\DI\Definitions\Statement || $val instanceof DynamicParameter) {
$val = null;
}
});
$builder = $this->getContainerBuilder();
$dynamicParams = $this->dynamicParams;
foreach ($builder->parameters as $key => $value) {
$value = [$value];
array_walk_recursive($value, function ($val) use (&$dynamicParams, $key): void {
if ($val instanceof DynamicParameter || $val instanceof Nette\DI\Definitions\Statement) {
$dynamicParams[] = $key;
}
});
}
$dynamicParams = array_values(array_unique($dynamicParams));

$method = Method::from([Container::class, 'getStaticParameters'])
->addBody('return ?;', [array_diff_key($builder->parameters, array_flip($dynamicParams))]);
$class->addMember($method);

if (!$dynamicParams) {
return;
}

$resolver = new Nette\DI\Resolver($builder);
$generator = new Nette\DI\PhpGenerator($builder);
$method = Method::from([Container::class, 'getDynamicParameter']);
$class->addMember($method);
$method->addBody('switch (true) {');
foreach ($dynamicParams as $key) {
$value = Nette\DI\Helpers::expand($this->config[$key] ?? null, $builder->parameters);
$value = $resolver->completeArguments(Nette\DI\Helpers::filterArguments([$value]));
$method->addBody("\tcase \$key === ?: return ?;", [$key, $generator->convertArguments($value)[0]]);
}
$method->addBody("\tdefault: return parent::getDynamicParameter(\$key);\n};");

$method = Method::from([Container::class, 'getParameters']);
$class->addMember($method);
$method->addBody('array_map(function ($key) { $this->getParameter($key); }, ?);', [$dynamicParams]);
$method->addBody('return parent::getParameters();');

$cnstr = $class->getMethod('__construct');
$cnstr->addBody('$this->parameters += ?;', [$parameters]);
foreach ($this->dynamicValidators as [$param, $expected]) {
if ($param instanceof Nette\DI\Definitions\Statement) {
continue;
if (!$param instanceof Nette\DI\Definitions\Statement) {
$this->initialization->addBody('Nette\Utils\Validators::assert(?, ?, ?);', [$param, $expected, 'dynamic parameter']);
}

$cnstr->addBody('Nette\Utils\Validators::assert(?, ?, ?);', [$param, $expected, 'dynamic parameter']);
}
}
}
13 changes: 10 additions & 3 deletions src/DI/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,15 @@ private static function expandString(string $var, array $params, $recursive = fa
foreach (explode('.', $part) as $key) {
if (is_array($val) && array_key_exists($key, $val)) {
$val = $val[$key];
} elseif ($val instanceof DynamicParameter) {
$val = new DynamicParameter($val . '[' . var_export($key, true) . ']');
if ($val instanceof DynamicParameter || $val instanceof Statement) {
$val = '$this->getParameter';
foreach (explode('.', $part) as $i => $key) {
$key = var_export($key, true);
$val .= $i ? "[$key]" : "($key)";
}
$val = new DynamicParameter($val);
break;
}
} else {
throw new Nette\InvalidArgumentException(sprintf("Missing parameter '%s'.", $part));
}
Expand Down Expand Up @@ -116,7 +123,7 @@ private static function expandString(string $var, array $params, $recursive = fa
$res = array_filter($res, function ($val): bool { return $val !== ''; });
$res = array_map(function ($val): string {
return $val instanceof DynamicParameter
? "($val)"
? (string) $val
: var_export((string) $val, true);
}, $res);
return new DynamicParameter(implode(' . ', $res));
Expand Down
8 changes: 7 additions & 1 deletion src/DI/PhpGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ public function formatStatement(Statement $statement): string
* @internal
*/
public function formatPhp(string $statement, array $args): string
{
return (new Php\Dumper)->format($statement, ...$this->convertArguments($args));
}


public function convertArguments(array $args): array
{
array_walk_recursive($args, function (&$val): void {
if ($val instanceof Statement) {
Expand All @@ -186,7 +192,7 @@ public function formatPhp(string $statement, array $args): string
}
}
});
return (new Php\Dumper)->format($statement, ...$args);
return $args;
}


Expand Down
29 changes: 0 additions & 29 deletions tests/DI/Compiler.dynamicParameters.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,6 @@ test('Array item as dynamic parameter within string expansion', function () {


test('Class constant as parameter', function () {
$compiler = new DI\Compiler;
$compiler->setDynamicParameterNames(['dynamic']);
$container = createContainer($compiler, '
parameters:
dynamic: ::trim(" a ")
services:
one: Service(%dynamic%)
');
Assert::same('a', $container->getService('one')->arg);
});


test('', function () {
$compiler = new DI\Compiler;
$compiler->setDynamicParameterNames(['dynamic']);
$container = createContainer($compiler, '
Expand All @@ -126,18 +112,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'.");
});
15 changes: 10 additions & 5 deletions tests/DI/Compiler.dynamicParameters.validator.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ test("Dynamic parameter of type int given to 'string' configuration", 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.');
});

Expand All @@ -42,11 +43,12 @@ test("Dynamic parameter of type null given to 'string' configuration", 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.');
});

Expand All @@ -56,11 +58,12 @@ test("Dynamic sub-parameter of type int given to 'string' configuration", functi
$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.');
});

Expand All @@ -70,11 +73,12 @@ test("Dynamic parameter of type int successfully given to 'int|null' configurati
$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();
});
});

Expand All @@ -84,10 +88,11 @@ test("Dynamic parameter of type null successfully given to 'int|null' configurat
$compiler->addExtension('foo', new FooExtension);
$compiler->setDynamicParameterNames(['dynamic']);
Assert::noError(function () use ($compiler) {
createContainer($compiler, '
$container = createContainer($compiler, '
foo:
key:
intnull: %dynamic%
', ['dynamic' => null]);
$container->initialize();
});
});
2 changes: 1 addition & 1 deletion tests/DI/Compiler.extension.schema.dynamic.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,5 @@ test('Statement via parameter', function () {
foo:
key: %dynamic%
');
Assert::type(Nette\DI\Definitions\Statement::class, $foo->getConfig()->key);
Assert::type(Nette\DI\DynamicParameter::class, $foo->getConfig()->key);
});
16 changes: 16 additions & 0 deletions tests/DI/Compiler.extension.schema.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,19 @@ test('Extension without configuration', function () {
');
Assert::equal((object) ['key' => null], $foo->getConfig());
});


test('Extension with parameter expansion', function () {
$compiler = new Nette\DI\Compiler;
$compiler->addExtension('foo', $foo = new FooExtension);
createContainer($compiler, '
parameters:
foo:
scalar: hello
dynamic: ::trim(x)
foo:
key: %foo.scalar%
');
Assert::equal((object) ['key' => 'hello'], $foo->getConfig());
});
Loading

0 comments on commit 5d29ef2

Please sign in to comment.