Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recursive dependency detection #13

Merged
merged 7 commits into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 52 additions & 17 deletions src/DelegatingContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,43 +38,88 @@ public function __construct(ServiceProviderInterface $provider, PsrContainerInte
* {@inheritDoc}
*/
public function get($id)
{
static $stack = [];
XedinUnknown marked this conversation as resolved.
Show resolved Hide resolved

if (array_key_exists($id, $stack)) {
$trace = implode(' -> ', array_keys($stack)) . ' -> ' . $id;

throw new ContainerException(
$this->__("Circular dependency detected:\n%s", [$trace]),
0,
null
);
}

$stack[$id] = true;

try {
return $this->_createService($id);
} finally {
unset($stack[$id]);
}
}

/**
* {@inheritDoc}
*/
public function has($id)
{
$services = $this->provider->getFactories();

return array_key_exists($id, $services);
}

/**
* Creates a service, using the factory that corresponds to a specific key.
*
* @since [*next-version*]
*
* @param string $key The key of the service to be created.
*
* @return mixed The created service.
*
* @throws NotFoundException If no factory corresponds to the given $key.
* @throws ContainerException If an error occurred while creating the service.
*/
protected function _createService(string $key)
{
$provider = $this->provider;
$services = $provider->getFactories();

if (!array_key_exists($id, $services)) {
if (!array_key_exists($key, $services)) {
throw new NotFoundException(
$this->__('Service not found for key "%1$s"', [$id]),
$this->__('Service not found for key "%1$s"', [$key]),
0,
null
);
}

$service = $services[$id];
$service = $services[$key];

try {
$service = $this->_invokeFactory($service);
} catch (UnexpectedValueException $e) {
throw new ContainerException(
$this->__('Could not create service "%1$s"', [$id]),
$this->__('Could not create service "%1$s"', [$key]),
0,
$e
);
}

$extensions = $provider->getExtensions();

if (!array_key_exists($id, $extensions)) {
if (!array_key_exists($key, $extensions)) {
return $service;
}

$extension = $extensions[$id];
$extension = $extensions[$key];

try {
$service = $this->_invokeExtension($extension, $service);
} catch (UnexpectedValueException $e) {
throw new ContainerException(
$this->__('Could not extend service "%1$s"', [$id]),
$this->__('Could not extend service "%1$s"', [$key]),
0,
$e
);
Expand All @@ -83,16 +128,6 @@ public function get($id)
return $service;
}

/**
* {@inheritDoc}
*/
public function has($id)
{
$services = $this->provider->getFactories();

return array_key_exists($id, $services);
}

/**
* Retrieves a service by invoking its factory.
*
Expand Down
32 changes: 32 additions & 0 deletions test/functional/DelegatingContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Dhii\Container\TestHelpers\ServiceProviderMock;
use Exception;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;

class DelegatingContainerTest extends TestCase
Expand Down Expand Up @@ -87,4 +88,35 @@ public function testHasFalse()
$this->assertFalse($result, 'Wrongly determined not having');
}
}

public function testRecursiveDetection()
{
{
$provider = ServiceProviderMock::create($this, [
'a' => function (ContainerInterface $c) {
return $c->get('b');
},
'b' => function (ContainerInterface $c) {
return $c->get('c');
},
'c' => function (ContainerInterface $c) {
return $c->get('a');
},
]);

$subject = new DelegatingContainer($provider);
}
{
try {
$subject->get('a');
$this->fail('Expected exception to be thrown');
} catch (ContainerExceptionInterface $exception) {
$this->assertStringContainsString(
'a -> b -> c -> a',
$exception->getMessage(),
'Exception message does not properly report circular dependency'
);
}
}
}
}