A fresh take on the PHP dependency injection container, with all the features you expect.
- Modular — isolated or shared config scope per module
- Type-safe with local reasoning
- Autowiring at the module level
- Autoconfiguration — plug in your attributes or autoconfigure by type
- Tags with flexible resolution
- Lifetimes — singleton / canBeScoped / scoped
- Callable services — functions and methods as first-class services
- Variadic parameters supported
- PHP 8.4.12+
composer require thesis/dicThis guide covers only a fraction of what Dic can do — just enough to get you started.
The Thesis\Dic class is the heart of container configuration.
It lets you declare services, require modules, subscribe to events and more.
A module is the unit of composition: you compose your application from modules, and the application itself is just the root module.
A module is a class implementing Thesis\Dic\Module:
it receives a Dic, declares services, and returns whatever it exports — usually a Ref<T>.
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.
Use object() to declare an object service, and arg() to override individual constructor arguments:
use Psr\Log\NullLogger;
use Thesis\Dic;
use Thesis\Dic\Module;
final readonly class ConsoleModule implements Module
{
public function configure(Dic $dic)
{
$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.
A module can depend on services it doesn't declare itself: accept them as constructor arguments and wire them in.
use Psr\Log\LoggerInterface;
use Thesis\Dic;
use Thesis\Dic\Module;
use Thesis\Dic\Ref;
/**
* @implements Module<Ref<ConsoleApplication>>
*/
final readonly class ConsoleModule implements Module
{
/**
* @param Ref<LoggerInterface> $logger
*/
public function __construct(
private Ref $logger,
) {}
public function configure(Dic $dic): mixed
{
return $dic
->object(ConsoleApplication::class)
->arg('logger', $this->logger);
}
}To use a module inside another one, call import() and get whatever that module exports.
See Modularity for how import() isolates modules and when to use apply() instead.
use Psr\Log\NullLogger;
use Thesis\Dic;
use Thesis\Dic\Module;
use Thesis\Dic\Ref;
/**
* @implements Module<Ref<ConsoleApplication>>
*/
final readonly class MyApp implements Module
{
public function configure(Dic $dic): mixed
{
$logger = $dic->object(NullLogger::class);
return $dic->import(new ConsoleModule($logger));
}
}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: new MyApp(),
main: static fn (ConsoleApplication $cli) => $cli->run(),
);
exit($status);For tests and debugging, Dic::build() returns the resolved module's export without disposing anything:
use Testo\Assert;
use Thesis\Dic;
$cli = Dic::build(new MyApp());
Assert::instanceOf($cli, ConsoleApplication::class);- Objects — declaring object services, factories, post-construction calls, lazy instantiation
- Values — declaring ready-made values and refs as services
- Functions and closures — type-safe callable 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 —
Moduleinterface,import()andapply() - Lifetimes — singleton, scoped and canBeScoped lifetimes, and the
Scoped<T>handle - Disposal — releasing resources when a scope or the container is disposed