tereta / di
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.
Requires
- php: >=8.2
- psr/container: ^2.0
Requires (Dev)
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
- squizlabs/php_codesniffer: ^3.13
Suggests
None
Provides
Conflicts
None
Replaces
None
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, usecreate()for other classes.create()can not be called for a singleton.- Configure the container with
set()andsetSingleton()before the firstget(). A singleton that is already created is not recreated ifset()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, orget()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.