Minimal PSR-11 dependency injection container with reflection-based autowiring.
The container resolves entries lazily. You can register values, factories and class bindings explicitly, or let the container autowire a class straight from its name by reading its constructor signature through reflection. Every resolved entry is cached, so the container behaves as a shared (singleton) registry.
- PHP 8.1 or higher
psr/container^2.0
composer require initphp/containerrequire_once 'vendor/autoload.php';
use InitPHP\Container\Container;
class Mailer
{
}
class UserService
{
public function __construct(public Mailer $mailer)
{
}
}
$container = new Container();
// No registration needed: the container reads UserService's constructor,
// builds the Mailer dependency automatically and injects it.
$service = $container->get(UserService::class);
var_dump($service instanceof UserService); // bool(true)
var_dump($service->mailer instanceof Mailer); // bool(true)When you call get() with an existing class name, the container instantiates it and recursively resolves every class-typed constructor argument:
$service = $container->get(UserService::class);The resolved instance is cached. Asking for the same identifier again returns the exact same object:
$container->get(Mailer::class) === $container->get(Mailer::class); // trueset() accepts any value. Scalars, arrays and objects are returned as they were stored:
$container->set('app.name', 'InitPHP');
$container->set('config', ['debug' => true]);
$container->set('logger', new FileLogger('/var/log/app.log'));
$container->get('app.name'); // 'InitPHP'
$container->get('config'); // ['debug' => true]
$container->get('logger'); // the same FileLogger instanceRegister a Closure to build an entry lazily. The closure receives the container and runs only once, the first time the entry is requested:
use Psr\Container\ContainerInterface;
$container->set('pdo', function (ContainerInterface $c) {
return new PDO('sqlite::memory:');
});
$pdo = $container->get('pdo'); // closure runs here, result is cachedBind an interface (or any identifier) to a concrete class name so that both direct lookups and autowired dependencies resolve to the implementation:
interface LoggerInterface
{
}
class FileLogger implements LoggerInterface
{
}
class Report
{
public function __construct(public LoggerInterface $logger)
{
}
}
$container->set(LoggerInterface::class, FileLogger::class);
$container->get(LoggerInterface::class); // FileLogger instance
$container->get(Report::class)->logger; // the same FileLogger instancehas() returns true when get() would not throw a NotFoundException — that is, the identifier is a registered entry or an existing class name:
$container->has('app.name'); // true after set()
$container->has(UserService::class); // true (autowirable class)
$container->has('missing'); // falseget($id) resolves in this order:
- Return the cached instance if
$idwas resolved before. - If
$idwas registered withset(), build it (invoke the closure, instantiate the class name, or return the stored value) and cache it. - If
$idis an existing class name, autowire it and cache it. - Otherwise throw
NotFoundException.
Constructor parameters are resolved as follows: a class-typed parameter is fetched from the container; otherwise its default value is used; otherwise null is supplied for nullable parameters. If none of these apply, resolution fails.
All exceptions live in InitPHP\Container\Exception and implement the relevant PSR-11 interface.
| Exception | Implements | Thrown when |
|---|---|---|
NotFoundException |
Psr\Container\NotFoundExceptionInterface |
The identifier is neither registered nor an existing class. |
DependencyIsNotInstantiableException |
Psr\Container\ContainerExceptionInterface |
The target is an interface, abstract class, or has a non-public constructor. |
DependencyHasNoDefaultValueException |
Psr\Container\ContainerExceptionInterface |
A constructor parameter cannot be autowired and has no default or nullable fallback. |
CircularDependencyException |
Psr\Container\ContainerExceptionInterface |
A class depends on itself directly or through a cycle. |
ContainerException is the base class for NotFoundException and the three resolution exceptions, so you can catch every container error with a single catch (ContainerException $e).
In-depth guides with runnable examples live in the docs/ directory:
composer test # PHPUnit
composer stan # PHPStan (max level)
composer cs-check # PHP-CS-Fixer (dry run)Contributions are welcome. Please read the org-wide CONTRIBUTING guide before opening a pull request.
Released under the MIT License.