Search by

Dependency injection container with a PSR-11 interface.It stores services, creates objects with their dependencies and returns the same instance when it is needed again.

0.0.4 2026-10-04 14:26 UTC

This package is not auto-updated.

Last update: 2026-10-04 11:27:04 UTC


README

🌐 English | Русский | Π£ΠΊΡ€Π°Ρ—Π½ΡΡŒΠΊΠ°

Dependency container for PHP with a PSR-11 interface. It creates objects together with everything they need in the constructor.

Requirements

PHP 8.2+, psr/container ^2.0.

Installation

composer require tereta/di

Quick start

use Tereta\Di\Container;

class Mailer
{
}

class Newsletter
{
    public function __construct(private Mailer $mailer)
    {
    }
}

$container = new Container();
$newsletter = $container->create(Newsletter::class);

The container sees that Newsletter needs a Mailer, creates it and passes it to the constructor.

New object - create()

create() returns a new object every time. The second parameter lets you pass constructor values by parameter name:

class Report
{
    public function __construct(private int $id, private string $format = 'pdf')
    {
    }
}

$report = $container->create(Report::class, ['id' => 15]);
$csv = $container->create(Report::class, ['id' => 15, 'format' => 'csv']);

If a value is not passed, the default value from the constructor is used.

Passed values go deeper: nested objects that the container creates for the constructor receive them too. A value is matched by parameter name, and an object also by type:

class Connection
{
    public function __construct(private string $host = 'localhost')
    {
    }
}

class Repository
{
    public function __construct(private Connection $connection, private string $table = 'users')
    {
    }
}

$repository = $container->create(Repository::class, ['host' => 'db.local', 'table' => 'orders']);
// Repository gets table = orders, and the nested Connection gets host = db.local
  • One name gets one value on all levels, different values for different levels can not be set.
  • Singletons do not receive passed values, they use only the parameters from setSingleton().

One shared object - setSingleton() and get()

Some objects are needed as a single instance for the whole application, for example a database connection outside swoole or in standard mode. Such a class is registered with setSingleton() and retrieved with get():

$container->setSingleton(Database::class, ['dsn' => 'mysql:host=localhost;dbname=app']);

$db = $container->get(Database::class);
$same = $container->get(Database::class); // the same object
  • Singleton parameters are set only in setSingleton(), get() takes no parameters.
  • The object is created on the first get() call, not on registration.
  • If another object needs the class in its constructor, it gets this shared instance too.
  • get() works only with registered singletons, use create() for other classes.
  • create() can not be called for a singleton.
  • Configure the container with set() and setSingleton() before the first get(). A singleton that is already created is not recreated if set() is changed later.

Interface and its implementation - set()

If a constructor expects an interface or a class override, tell the container which class to create:

interface LoggerInterface
{
}

class FileLogger implements LoggerInterface
{
}

class Service
{
    public function __construct(private LoggerInterface $logger)
    {
    }
}

$container->set(LoggerInterface::class, FileLogger::class);

$service = $container->create(Service::class); // gets FileLogger

set() also works together with singletons:

$container
    ->set(DatabaseInterface::class, Database::class)
    ->setSingleton(Database::class, ['dsn' => 'mysql:host=localhost;dbname=app']);

$db = $container->get(DatabaseInterface::class);

A singleton can also be registered on the interface - then one shared object is used both for the interface and for the class:

$container
    ->set(DatabaseInterface::class, Database::class)
    ->setSingleton(DatabaseInterface::class, ['dsn' => 'mysql:host=localhost;dbname=app']);

$container->get(DatabaseInterface::class); // shared Database object
$container->get(Database::class);          // the same object
$container->has(Database::class);          // true

If two interfaces point to one class and both are registered as singletons, each of them has its own object. Then get(Database::class) throws a Runtime error, because it is not clear which object to return.

Only get() returns the shared object by class. create(Database::class) and constructors that expect exactly the Database class, not the interface, get a separate new object. If the shared object is needed everywhere, register the class: setSingleton(Database::class, [...]).

Check - has()

has() tells whether a singleton object can be retrieved with get():

$container->has(Database::class); // true if registered with setSingleton()

Interfaces

Tereta\Di\Interfaces\Factory - an interface with the create() method. Use it in your classes when you need to create objects but do not need the whole container:

use Tereta\Di\Interfaces\Factory as FactoryInterface;

class ReportService
{
    public function __construct(private FactoryInterface $factory)
    {
    }

    public function make(int $id): Report
    {
        return $this->factory->create(Report::class, ['id' => $id]);
    }
}

$service = $container->create(ReportService::class);

The container passes itself into parameters of the Factory, Provider, Psr\Container\ContainerInterface or Container type.

Tereta\Di\Interfaces\Resolver - the interface of the part that builds objects for the container. You can pass your own implementation to the container constructor, for example to log object creation:

$container = new Container(new MyResolver());

Tereta\Di\Interfaces\Provider - the interface the resolver uses to ask the container for nested objects. The container implements it, so a custom resolver gets the container itself.

Errors

  • Tereta\Di\Exceptions\NotFound - the class is not found, or get() is called for a class that is not registered as a singleton.
  • Tereta\Di\Exceptions\Circular - classes depend on each other in a circle.
  • Tereta\Di\Exceptions\Runtime - the object could not be created, for example a constructor parameter has no value.

The error message contains the chain of classes where the problem happened.