thesis / di
Thesis DI Container
Fund package maintenance!
Requires
- php: ^8.4
- thesis/formatter: ^0.1
- typhoon/type: ^0.8.1
Requires (Dev)
- phpunit/phpunit: ^13.0
- symfony/var-dumper: ^8.0.4
README
A fresh take on the PHP dependency injection container, with all the features you expect.
- Modular — isolated modules, no global scope
- Type-safe with local reasoning
- Autowiring at the module level
- Autoconfiguration — plug in your attributes or autoconfigure by type
- Tags with flexible resolution
- Scoped service lifetimes
- Callable services (closures, methods, …)
- Variadic parameters supported
Contents
Installation
composer require thesis/dic
Quick start
This guide covers only a fraction of what Dic can do — just enough to get you started.
Dic
The Thesis\Dic class is the heart of container configuration.
It lets you declare services, require modules, subscribe to events and more.
Module
A module is the unit of composition: you assemble your application from modules, and the application itself is just the root module.
A module is a callable that accepts Dic and returns whatever it exports.
Ref
No string identifiers to invent — they aren't type-safe.
Instead, declaring a service returns a Thesis\Dic\Ref<T>, its handle and identifier,
with T inferred from configuration:
$logger = $dic->object(NullLogger::class); // Ref<NullLogger>
A ref is the service's identity: a distinct ref is a distinct service. Assign it to a variable and pass it around to inject, import or export.
Declaring services
Use object() to declare an object service, and arg() to override individual constructor arguments:
use Psr\Log\NullLogger; use Thesis\Dic; function consoleModule(Dic $dic): void { $logger = $dic->object(NullLogger::class); $dic ->object(ConsoleApplication::class) ->arg('logger', $logger); }
The $logger ref is passed as a constructor argument — that's how services get wired together.
See Arguments for named, positional and variadic arguments.
Putting it all together
A module can depend on services it doesn't declare itself: typehint them as Ref<T> and wire them in.
use Psr\Log\LoggerInterface; use Thesis\Dic; use Thesis\Dic\Ref; final readonly class ConsoleModule { /** * @param Ref<LoggerInterface> $logger */ public function __construct( private Ref $logger, ) {} /** * @return Ref<ConsoleApplication> */ public function __invoke(Dic $dic): Ref { return $dic ->object(ConsoleApplication::class) ->arg('logger', $this->logger); } }
To use a module inside another one, call require() and get whatever that module exports.
See Modularity for how require isolates modules and when you might share autowiring instead.
use Psr\Log\NullLogger; use Thesis\Dic; use Thesis\Dic\Ref; /** * @return Ref<ConsoleApplication> */ function myApp(Dic $dic): Ref { $logger = $dic->object(NullLogger::class); $cli = $dic->require(new ConsoleModule($logger)); return $cli; }
To run an application, pass the root module to Dic::run() together with $main —
a function that receives the resolved service.
The container builds, calls $main, and disposes everything afterwards — even on failure:
use Thesis\Dic; $status = Dic::run( module: myApp(...), main: static fn (ConsoleApplication $cli) => $cli->run(), ); exit($status);
For tests and debugging, Dic::assemble() returns the resolved module's export without disposing anything:
use Testo\Assert; use Thesis\Dic; $cli = Dic::assemble(myApp(...)); Assert::instanceOf($cli, ConsoleApplication::class);
Documentation
- Objects — declaring object services, factories, post-construction calls, lazy instantiation
- Values — declaring ready-made values and refs as services
- Closures — type-safe closure services, runtime parameters and dependencies
- Arguments — named, positional and variadic arguments
- Autowiring — binding services to types and qualifiers
- Tags — tagging, collecting tagged services, tag resolution and autoconfiguration
- Modularity — composing modules with
require, and when to share autowiring - Lifetimes — singleton, scoped and canBeScoped lifetimes, and the
Scoped<T>handle - Disposal — releasing resources when a scope or the container is disposed