Skip to content

Commit

Permalink
Container: detects circular reference for parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Oct 15, 2023
1 parent 3e310da commit c2fa779
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 18 deletions.
41 changes: 25 additions & 16 deletions src/DI/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class Container
/** @var object[] service name => instance */
private $instances = [];

/** @var array circular reference detector */
/** @var array<string, true> circular reference detector */
private $creating;

/** @var array<string, string|\Closure> */
Expand All @@ -66,7 +66,9 @@ public function getParameters(): array
public function getParameter($key)
{
if (!array_key_exists($key, $this->parameters)) {
$this->parameters[$key] = $this->getDynamicParameter($key);
$this->parameters[$key] = $this->preventDeadLock("%$key%", function () use ($key) {
return $this->getDynamicParameter($key);
});
}
return $this->parameters[$key];
}
Expand Down Expand Up @@ -219,28 +221,21 @@ public function createService(string $name, array $args = []): object
{
$name = $this->aliases[$name] ?? $name;
$method = self::getMethodName($name);
$cb = $this->methods[$method] ?? null;
if (isset($this->creating[$name])) {
throw new Nette\InvalidStateException(sprintf('Circular reference detected for services: %s.', implode(', ', array_keys($this->creating))));

} elseif ($cb === null) {
$callback = $this->methods[$method] ?? null;
if ($callback === null) {
throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
}

try {
$this->creating[$name] = true;
$service = $cb instanceof \Closure
? $cb(...$args)
$service = $this->preventDeadLock($name, function () use ($callback, $args, $method) {
return $callback instanceof \Closure
? $callback(...$args)
: $this->$method(...$args);

} finally {
unset($this->creating[$name]);
}
});

if (!is_object($service)) {
throw new Nette\UnexpectedValueException(sprintf(
"Unable to create service '$name', value returned by %s is not object.",
$cb instanceof \Closure ? 'closure' : "method $method()"
$callback instanceof \Closure ? 'closure' : "method $method()"
));
}

Expand Down Expand Up @@ -326,6 +321,20 @@ public function findByTag(string $tag): array
}


private function preventDeadLock(string $key, \Closure $callback)
{
if (isset($this->creating[$key])) {
throw new Nette\InvalidStateException(sprintf('Circular reference detected for: %s.', implode(', ', array_keys($this->creating))));
}
try {
$this->creating[$key] = true;
return $callback();
} finally {
unset($this->creating[$key]);
}
}


/********************* autowiring ****************d*g**/


Expand Down
2 changes: 1 addition & 1 deletion tests/DI/Compiler.parameters.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,6 @@ test('Detecting circular references', function () {
$container->getParameter('bar');
},
Nette\InvalidStateException::class,
'Circular reference detected for services: one.'
'Circular reference detected for: %bar%, one.'
);
});
2 changes: 1 addition & 1 deletion tests/DI/Container.circular.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ $container = new MyContainer;

Assert::exception(function () use ($container) {
$container->getService('one');
}, Nette\InvalidStateException::class, 'Circular reference detected for services: one, two.');
}, Nette\InvalidStateException::class, 'Circular reference detected for: one, two.');

0 comments on commit c2fa779

Please sign in to comment.