Skip to content

Errors and dependency chains

Every exception the container raises implements ContainerExceptionInterface from Contract, and extends the base Exception from Error. You can therefore catch a single interface to handle any container failure:

php
<?php
declare(strict_types=1);

use Raxos\Contract\Container\ContainerExceptionInterface;

try {
    $service = $container->get(ReportService::class);
} catch (ContainerExceptionInterface $err) {
    // Handle any resolution failure.
}

The exceptions

ExceptionRaised when
DependencyCannotInstantiateExceptionThe target is an interface or abstract class with no binding, so it cannot be constructed.
DependencyCannotAutowireExceptionA built-in typed parameter or #[Env] value cannot be resolved and has no fallback.
TaggedDependencyNotFoundExceptionA tagged dependency was requested but no binding exists for that tag.
CircularDependencyDetectedExceptionA class depends on itself, directly or through a cycle of other classes.
InvalidCallableExceptioncall received a value that is not a closure, a valid [target, method] pair, or an invokable class string.
AutowireFailedExceptionAn unexpected error occurred while autowiring a parameter.
ReflectionFailedExceptionAn underlying ReflectionException was thrown; it also implements ReflectionFailedExceptionInterface.
NotFoundExceptionUsed by the PSR-11 adapter; implements the PSR-11 NotFoundExceptionInterface.

Circular dependencies

While resolving, the container records each class, function and method it visits. If it encounters one it is already resolving, it raises a CircularDependencyDetectedException instead of recursing forever. Breaking the cycle with a #[Proxy] parameter is often the simplest fix, since the proxied dependency is only built on first use.

Dependency chains

Outside production mode, the container attaches a DependencyChain to the exceptions that describe a missing or unresolvable dependency. The chain records where resolution started (the file and line of the get or call) and the ordered path of dependencies that led to the failure:

php
<?php
declare(strict_types=1);

use Raxos\Container\Error\DependencyCannotAutowireException;

try {
    $container->get(ReportService::class);
} catch (DependencyCannotAutowireException $err) {
    $chain = $err->chain;

    if ($chain !== null) {
        echo "Started at {$chain->fileName}:{$chain->line}\n";
        echo "Failed on {$chain->last()->name}\n";
    }
}

DependencyChain exposes first() and last() for the ends of the path, and toArray() for the full list of Dependency value objects. Each Dependency carries a name, shortName and typeName, and an equals() method to compare two of them. When the container runs with production: true, no chain is tracked and the chain property is null.

TIP

Keep production set to false in development so failures come with a full dependency chain, and set it to true in production to skip the tracking overhead.

Exception properties

The table above says when each exception is raised. Each one also carries public readonly properties that a catch block can read to report the failure. For the full DependencyChain and Dependency signatures, see the DependencyChain reference.

ExceptionProperties
DependencyCannotInstantiateException?DependencyChain $chain, ?Dependency $dependency
DependencyCannotAutowireException?DependencyChain $chain, ?Dependency $dependency
TaggedDependencyNotFoundException?DependencyChain $chain, ?Dependency $dependency, string $tag
CircularDependencyDetectedException?DependencyChain $chain, ?Dependency $dependency
InvalidCallableExceptionmixed $callable
AutowireFailedExceptionThrowable $err
ReflectionFailedExceptionReflectionException $err
NotFoundExceptionstring $id

The three resolution failures and CircularDependencyDetectedException expose the chain and dependency that failed. TaggedDependencyNotFoundException adds the requested tag. InvalidCallableException hands back the offending callable. AutowireFailedException and ReflectionFailedException wrap the underlying throwable in err, and NotFoundException reports the missing id.

php
<?php
declare(strict_types=1);

use Raxos\Container\Error\TaggedDependencyNotFoundException;

try {
    $container->get(CacheStore::class);
} catch (TaggedDependencyNotFoundException $err) {
    echo "No binding for tag {$err->tag}\n";

    if ($err->dependency !== null) {
        echo "While resolving {$err->dependency->name}\n";
    }
}