Dependency Injection Container
Table of content
- Dependency Injection Container
- Table of content
- About
- Registering services explicitly
- Resolving services
- Auto-registration
- Disabling auto-registration
- Exceptions
- Testing
About
GuiBranco\Pancake\DIContainer is a minimal PSR-11 compliant dependency injection container. It supports explicit service registration (singleton or transient) as well as auto-registration: resolving classes that were never registered by reflecting on their constructor and recursively wiring up their dependencies.
Registering services explicitly
use GuiBranco\Pancake\DIContainer;
$container = new DIContainer();
// Resolved once; the same instance is returned on every get() call.
$container->registerSingleton(Config::class, function () {
return new Config(['env' => 'prod']);
});
// Resolved again on every get() call.
$container->registerTransient('requestId', function () {
return uniqid('req_', true);
});
// register() defaults to transient; pass true as the third argument for a singleton.
$container->register('logger', function ($c) {
return new Logger($c->get(Config::class));
}, true);
Resolving services
$config = $container->get(Config::class);
$hasLogger = $container->has('logger');
Resolver callables receive the container itself as their only argument, so services can depend on other registered services.
Auto-registration
Auto-registration is enabled by default. Calling get() with a class name that was never registered reflects on its constructor and recursively resolves each typed, non-builtin parameter through the container — auto-registering those too, if needed. Scalar parameters fall back to their default value, or null if the parameter is nullable.
class ServiceC
{
}
class ServiceB
{
public function __construct(private ServiceC $serviceC)
{
}
}
class ServiceA
{
public function __construct(private ServiceB $serviceB)
{
}
}
$container = new DIContainer();
// None of ServiceA, ServiceB, or ServiceC were registered — the whole chain is
// auto-registered and resolved recursively.
$serviceA = $container->get(ServiceA::class);
Mix explicit registration and auto-registration freely — an auto-registered class can depend on a service you registered explicitly:
$container->registerSingleton(Config::class, function () {
return new Config(['env' => 'prod']);
});
// Config resolves to the singleton above; Service itself is auto-registered.
$service = $container->get(Service::class);
Interfaces and abstract classes are never auto-registrable — register a concrete implementation for them explicitly.
Disabling auto-registration
Pass false to the constructor, or call setAutoRegisterEnabled() at any point:
// Off from the start: every dependency must be registered explicitly.
$container = new DIContainer(autoRegisterEnabled: false);
// Or toggle it at runtime.
$container = new DIContainer();
$container->setAutoRegisterEnabled(false);
With auto-registration disabled, get() still resolves anything registered explicitly — it only stops falling back to reflection for unregistered class names, throwing NotFoundException instead.
Exceptions
get() throws:
GuiBranco\Pancake\Exceptions\NotFoundException(implementsPsr\Container\NotFoundExceptionInterface) when the identifier is neither registered nor auto-registrable — either because it isn't a class, or because auto-registration is disabled.GuiBranco\Pancake\Exceptions\ContainerException(implementsPsr\Container\ContainerExceptionInterface) when a resolver throws, a constructor dependency can't be determined, or the class isn't instantiable.
Testing
See tests/Unit/DIContainerTest.php and tests/Integration/DIContainerIntegrationTest.php.