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

Improve usage NotFoundException for cases with definitions #356

Merged
merged 5 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 1.2.2 under development

- Enh #353: Add shortcut for tag reference #333 (@xepozz)
- Enh #356: Improve usage `NotFoundException` for cases with definitions (@vjik)

## 1.2.1 December 23, 2022

Expand Down
12 changes: 10 additions & 2 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,17 @@
try {
try {
$this->instances[$id] = $this->build($id);
} catch (NotFoundExceptionInterface $e) {
} catch (NotFoundExceptionInterface $exception) {
if (!$this->delegates->has($id)) {
throw $e;
if ($exception instanceof NotFoundException) {
if ($id !== $exception->getId()) {
$buildStack = $exception->getBuildStack();
array_unshift($buildStack, $id);
throw new NotFoundException($exception->getId(), $buildStack);
}
throw $exception;
}
throw new NotFoundException($id, [$id], previous: $exception);
}

/** @psalm-suppress MixedReturnStatement */
Expand Down Expand Up @@ -276,7 +284,7 @@

$this->delegates->attach($delegate);
}
$this->definitions->setDelegateContainer($this->delegates);

Check warning on line 287 in src/Container.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "MethodCallRemoval": --- Original +++ New @@ @@ } $this->delegates->attach($delegate); } - $this->definitions->setDelegateContainer($this->delegates); + } /** * @param mixed $definition Definition to validate.

Check warning on line 287 in src/Container.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "MethodCallRemoval": --- Original +++ New @@ @@ } $this->delegates->attach($delegate); } - $this->definitions->setDelegateContainer($this->delegates); + } /** * @param mixed $definition Definition to validate.
}

/**
Expand All @@ -303,7 +311,7 @@

$definition = array_merge(
$class === null ? [] : [ArrayDefinition::CLASS_NAME => $class],
[ArrayDefinition::CONSTRUCTOR => $constructorArguments],

Check warning on line 314 in src/Container.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": --- Original +++ New @@ @@ $methodsAndProperties = $definition['methodsAndProperties']; $definition = array_merge( $class === null ? [] : [ArrayDefinition::CLASS_NAME => $class], - [ArrayDefinition::CONSTRUCTOR => $constructorArguments], + [], // extract only value from parsed definition method array_map(fn(array $data): mixed => $data[2], $methodsAndProperties) );

Check warning on line 314 in src/Container.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "ArrayItemRemoval": --- Original +++ New @@ @@ $methodsAndProperties = $definition['methodsAndProperties']; $definition = array_merge( $class === null ? [] : [ArrayDefinition::CLASS_NAME => $class], - [ArrayDefinition::CONSTRUCTOR => $constructorArguments], + [], // extract only value from parsed definition method array_map(fn(array $data): mixed => $data[2], $methodsAndProperties) );
// extract only value from parsed definition method
array_map(fn (array $data): mixed => $data[2], $methodsAndProperties),
);
Expand Down Expand Up @@ -489,7 +497,7 @@
);
}

$this->building[$id] = 1;

Check warning on line 500 in src/Container.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "IncrementInteger": --- Original +++ New @@ @@ } throw new CircularReferenceException(sprintf('Circular reference to "%s" detected while building: %s.', $id, implode(', ', array_keys($this->building)))); } - $this->building[$id] = 1; + $this->building[$id] = 2; try { /** @var mixed $object */ $object = $this->buildInternal($id);

Check warning on line 500 in src/Container.php

View workflow job for this annotation

GitHub Actions / mutation / PHP 8.1-ubuntu-latest

Escaped Mutant for Mutator "IncrementInteger": --- Original +++ New @@ @@ } throw new CircularReferenceException(sprintf('Circular reference to "%s" detected while building: %s.', $id, implode(', ', array_keys($this->building)))); } - $this->building[$id] = 1; + $this->building[$id] = 2; try { /** @var mixed $object */ $object = $this->buildInternal($id);
try {
/** @var mixed $object */
$object = $this->buildInternal($id);
Expand Down
28 changes: 22 additions & 6 deletions src/NotFoundException.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Exception;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;

/**
* `NotFoundException` is thrown when no definition or class was found in the container for a given ID.
Expand All @@ -18,19 +19,34 @@ final class NotFoundException extends Exception implements NotFoundExceptionInte
*/
public function __construct(
private string $id,
array $buildStack = []
private array $buildStack = [],
?Throwable $previous = null,
) {
$message = $id;
if ($buildStack !== []) {
$last = end($buildStack);
$message = sprintf('%s" while building %s', $last, '"' . implode('" -> "', $buildStack));
if (empty($this->buildStack)) {
$message = sprintf('No definition or class found or resolvable for "%s".', $id);
} elseif ($this->buildStack === [$id]) {
$message = sprintf('No definition or class found or resolvable for "%s" while building it.', $id);
} else {
$message = sprintf(
'No definition or class found or resolvable for "%s" while building "%s".',
end($this->buildStack),
implode('" -> "', $buildStack),
);
}

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

public function getId(): string
{
return $this->id;
}

/**
* @return string[]
*/
public function getBuildStack(): array
{
return $this->buildStack;
}
}
2 changes: 1 addition & 1 deletion tests/Unit/CompositePsrContainerOverYiisoftTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public function testNotFoundException(): void
$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\".");
$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 it.\n 2. Container Yiisoft\Di\Container #$container2Id: No definition or class found or resolvable for \"test\" while building it.");
$compositeContainer->get('test');
}
}
58 changes: 58 additions & 0 deletions tests/Unit/ContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Psr\Container\NotFoundExceptionInterface;
use RuntimeException;
use stdClass;
use Throwable;
use Yiisoft\Di\BuildingException;
use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\Container;
Expand Down Expand Up @@ -51,6 +52,7 @@
use Yiisoft\Definitions\Exception\InvalidConfigException;
use Yiisoft\Definitions\Reference;
use Yiisoft\Injector\Injector;
use Yiisoft\Test\Support\Container\SimpleContainer;

/**
* ContainerTest contains tests for \Yiisoft\Di\Container
Expand Down Expand Up @@ -1972,4 +1974,60 @@ public function testInvalidTags(string $message, array $tags): void
$this->expectExceptionMessageMatches($message);
new Container($config);
}

public static function dataNotFoundExceptionMessageWithDefinitions(): array
{
return [
'without-definition' => [[]],
'with-definition' => [[SportCar::class => SportCar::class]],
];
}

/**
* @dataProvider dataNotFoundExceptionMessageWithDefinitions
*/
public function testNotFoundExceptionMessageWithDefinitions(array $definitions): void
{
$config = ContainerConfig::create()->withDefinitions($definitions);
$container = new Container($config);

$this->expectException(NotFoundException::class);
$this->expectExceptionMessage(
'No definition or class found or resolvable for "'
. EngineInterface::class
. '" while building "'
. SportCar::class
. '" -> "'
. EngineInterface::class
. '".'
);
$container->get(SportCar::class);
}

public function testNotFoundExceptionWithNotYiiContainer(): void
{
$config = ContainerConfig::create()
->withDefinitions([
ContainerInterface::class => new SimpleContainer(),
SportCar::class => SportCar::class,
]);
$container = new Container($config);

$exception = null;
try {
$container->get(SportCar::class);
} catch (Throwable $e) {
$exception = $e;
}

$this->assertInstanceOf(NotFoundException::class, $exception);
$this->assertSame(
'No definition or class found or resolvable for "' . SportCar::class . '" while building it.',
$exception->getMessage()
);
$this->assertInstanceOf(
\Yiisoft\Test\Support\Container\Exception\NotFoundException::class,
$exception->getPrevious()
);
}
}
Loading