Skip to content

Commit

Permalink
Improve CompositeContainer error (#274)
Browse files Browse the repository at this point in the history
Co-authored-by: Alexander Makarov <sam@rmcreative.ru>
  • Loading branch information
yiiliveext and samdark committed Nov 25, 2021
1 parent b1077f6 commit 3b8ce48
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 5 deletions.
21 changes: 20 additions & 1 deletion src/CompositeContainer.php
Expand Up @@ -6,6 +6,8 @@

use InvalidArgumentException;
use Psr\Container\ContainerInterface;
use RuntimeException;
use Throwable;
use function get_class;
use function gettype;
use function is_object;
Expand Down Expand Up @@ -63,7 +65,24 @@ public function get($id)
}
}

throw new NotFoundException($id);

// Collect details from containers
$exceptions = [];
foreach ($this->containers as $container) {
$hasException = false;
try {
$hasException = true;
$container->get($id);
} catch (Throwable $t) {
$exceptions[] = [$t, $container];
} finally {
if (!$hasException) {
$exceptions[] = [new RuntimeException('Container has() returned false but no exception was thrown from get().'), $container];
}
}
}

throw new CompositeNotFoundException($exceptions);
}

public function has($id): bool
Expand Down
33 changes: 33 additions & 0 deletions src/CompositeNotFoundException.php
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Di;

use Exception;
use Psr\Container\NotFoundExceptionInterface;

/**
* CompositeNotFoundException is thrown when no definition or class was found in the composite container for a given ID.
* It contains all exceptions thrown by containers registered in the composite container.
*/
final class CompositeNotFoundException extends Exception implements NotFoundExceptionInterface
{
/**
* @param array $exceptions Exceptions of containers in [throwable, container] format.
*/
public function __construct(array $exceptions)
{
$message = '';

foreach ($exceptions as $i => [$exception, $container]) {
$containerClass = get_class($container);
$containerId = spl_object_id($container);
$number = (int)$i + 1;

$message .= "\n $number. Container $containerClass #$containerId: {$exception->getMessage()}";
}

parent::__construct(sprintf('No definition or class found or resolvable in composite container:%s', $message));
}
}
4 changes: 2 additions & 2 deletions src/NotFoundException.php
Expand Up @@ -26,10 +26,10 @@ public function __construct(string $id, array $buildStack = [])
if ($buildStack !== []) {
$buildStack = array_keys($buildStack);
$last = end($buildStack);
$message = sprintf('%s while building %s', $last, implode(' -> ', $buildStack));
$message = sprintf('%s" while building %s', $last, '"' . implode('" -> "', $buildStack));
}

parent::__construct(sprintf('No definition or class found or resolvable for %s.', $message));
parent::__construct(sprintf('No definition or class found or resolvable for "%s".', $message));
}

public function getId(): string
Expand Down
26 changes: 26 additions & 0 deletions tests/Unit/CompositePsrContainerOverLeagueTest.php
Expand Up @@ -6,6 +6,8 @@

use League\Container\Container;
use Psr\Container\ContainerInterface;
use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\CompositeNotFoundException;

/**
* Test the CompositeContainer over League Container.
Expand All @@ -26,4 +28,28 @@ public function setupContainer(ContainerInterface $container, iterable $definiti

return $container;
}

protected function getExpectedNotFoundExceptionMessage(): string
{
return 'No definition or class found or resolvable in composite container:
#1: No definition or class found or resolvable for "test" while building "test".
#2: No definition or class found or resolvable for "test" while building "test".';
}

public function testNotFoundException(): void
{
$compositeContainer = new CompositeContainer();

$container1 = new Container();
$container1Id = spl_object_id($container1);
$container2 = new Container();
$container2Id = spl_object_id($container2);

$compositeContainer->attach($container1);
$compositeContainer->attach($container2);

$this->expectException(CompositeNotFoundException::class);
$this->expectExceptionMessage("No definition or class found or resolvable in composite container:\n 1. Container League\Container\Container #$container1Id: Alias (test) is not being managed by the container or delegates\n 2. Container League\Container\Container #$container2Id: Alias (test) is not being managed by the container or delegates");
$compositeContainer->get('test');
}
}
19 changes: 19 additions & 0 deletions tests/Unit/CompositePsrContainerOverYiisoftTest.php
Expand Up @@ -5,6 +5,8 @@
namespace Yiisoft\Di\Tests\Unit;

use Psr\Container\ContainerInterface;
use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\CompositeNotFoundException;
use Yiisoft\Di\Container;
use Yiisoft\Di\StateResetter;
use Yiisoft\Di\Tests\Support\EngineMarkOne;
Expand Down Expand Up @@ -64,4 +66,21 @@ public function testResetterInCompositeContainerWithExternalResetter(): void
$this->assertSame(42, $composite->get('engineMarkOne')->getNumber());
$this->assertSame(43, $composite->get('engineMarkTwo')->getNumber());
}

public function testNotFoundException(): void
{
$compositeContainer = new CompositeContainer();

$container1 = new Container();
$container1Id = spl_object_id($container1);
$container2 = new Container();
$container2Id = spl_object_id($container2);

$compositeContainer->attach($container1);
$compositeContainer->attach($container2);

$this->expectException(CompositeNotFoundException::class);
$this->expectExceptionMessage("No definition or class found or resolvable in composite container:\n 1. Container Yiisoft\Di\Container #$container1Id: No definition or class found or resolvable for \"test\" while building \"test\".\n 2. Container Yiisoft\Di\Container #$container2Id: No definition or class found or resolvable for \"test\" while building \"test\".");
$compositeContainer->get('test');
}
}
4 changes: 2 additions & 2 deletions tests/Unit/NotFoundExceptionTest.php
Expand Up @@ -20,15 +20,15 @@ public function testMessage(): void
{
$exception = new NotFoundException('test');

$this->assertSame('No definition or class found or resolvable for test.', $exception->getMessage());
$this->assertSame('No definition or class found or resolvable for "test".', $exception->getMessage());
}

public function testBuildStack(): void
{
$exception = new NotFoundException('test', ['a' => [], 'b' => [], 'test' => []]);

$this->assertSame(
'No definition or class found or resolvable for test while building a -> b -> test.',
'No definition or class found or resolvable for "test" while building "a" -> "b" -> "test".',
$exception->getMessage()
);
}
Expand Down

0 comments on commit 3b8ce48

Please sign in to comment.