Thesis DI Container

Maintainers

Package info

github.com/thesis-php/dic

pkg:composer/thesis/di

Fund package maintenance!

www.tinkoff.ru/cf/5MqZQas2dk7

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 3

Open Issues: 0

0.4.0 2026-04-23 19:13 UTC

This package is auto-updated.

Last update: 2026-06-28 10:39:18 UTC


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