README.md
<p align="center">
<a href="https://github.com/yiisoft" target="_blank">
<img src="https://yiisoft.github.io/docs/images/yii_logo.svg" height="100px" alt="Yii">
</a>
<h1 align="center">Yii Dependency Injection</h1>
<br>
</p>
[![Latest Stable Version](https://poser.pugx.org/yiisoft/di/v)](https://packagist.org/packages/yiisoft/di)
[![Total Downloads](https://poser.pugx.org/yiisoft/di/downloads)](https://packagist.org/packages/yiisoft/di)
[![Build status](https://github.com/yiisoft/di/actions/workflows/build.yml/badge.svg)](https://github.com/yiisoft/di/actions/workflows/build.yml)
[![Code coverage](https://codecov.io/gh/yiisoft/di/graph/badge.svg?token=P8W1UTwgQt)](https://codecov.io/gh/yiisoft/di)
[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fyiisoft%2Fdi%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/di/master)
[![static analysis](https://github.com/yiisoft/di/workflows/static%20analysis/badge.svg)](https://github.com/yiisoft/di/actions?query=workflow%3A%22static+analysis%22)
[![type-coverage](https://shepherd.dev/github/yiisoft/di/coverage.svg)](https://shepherd.dev/github/yiisoft/di)
[PSR-11](https://www.php-fig.org/psr/psr-11/) compatible
[dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) container that's able to instantiate
and configure classes resolving dependencies.
## Features
- [PSR-11](https://www.php-fig.org/psr/psr-11/) compatible.
- Supports property injection, constructor injection, and method injection.
- Detects circular references.
- Accepts array definitions. You can use it with mergeable configs.
- Provides optional autoload fallback for classes without explicit definition.
- Allows delegated lookup and has a composite container.
- Supports aliasing.
- Supports service providers.
- Has state resetter for long-running workers serving many requests, such as [RoadRunner](https://roadrunner.dev/)
or [Swoole](https://www.swoole.co.uk/).
- Supports container delegates.
## Requirements
- PHP 8.1 or higher.
- `Multibyte String` PHP extension.
## Installation
You could install the package with composer:
```shell
composer require yiisoft/di
```
## Using the container
Usage of the DI container is simple: You first initialize it with an
array of *definitions*. The array keys are usually interface names. It will
then use these definitions to create an object whenever the application requests that type.
This happens, for example, when fetching a type directly from the container
somewhere in the application. But objects are also created implicitly if a
definition has a dependency on another definition.
Usually one uses a single container for the whole application. It's often
configured either in the entry script such as `index.php` or a configuration
file:
```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
$config = ContainerConfig::create()
->withDefinitions($definitions);
$container = new Container($config);
```
You could store the definitions in a `.php` file that returns an array:
```php
return [
EngineInterface::class => EngineMarkOne::class,
'full_definition' => [
'class' => EngineMarkOne::class,
'__construct()' => [42],
'$propertyName' => 'value',
'setX()' => [42],
],
'closure' => fn (SomeFactory $factory) => $factory->create('args'),
'static_call_preferred' => fn () => MyFactory::create('args'),
'static_call_supported' => [MyFactory::class, 'create'],
'object' => new MyClass(),
];
```
You can define an object in several ways:
- In the simple case, an interface definition maps an id to a particular class.
- A full definition describes how to instantiate a class in more detail:
- `class` has the name of the class to instantiate.
- `__construct()` holds an array of constructor arguments.
- The rest of the config is property values (prefixed with `$`) and method calls, postfixed with `()`. They're
set/called in the order they appear in the array.
- Closures are useful if instantiation is tricky and can be better done in code. When using these, arguments are
auto-wired by type. `ContainerInterface` could be used to get current container instance.
- If it's even more complicated, it's a good idea to move such a code into a
factory and reference it as a static call.
- While it's usually not a good idea, you can also set an already
instantiated object into the container.
See [yiisoft/definitions](https://github.com/yiisoft/definitions) for more information.
After you configure the container, you can obtain a service via `get()`:
```php
/** @var \Yiisoft\Di\Container $container */
$object = $container->get('interface_name');
```
Note, however, that it's bad practice using a container directly. It's much
better to rely on auto-wiring as provided by the Injector available from the
[yiisoft/injector](https://github.com/yiisoft/injector) package.
## Using aliases
The DI container supports aliases via the `Yiisoft\Definitions\Reference` class.
This way you can retrieve objects by a more handy name:
```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
$config = ContainerConfig::create()
->withDefinitions([
EngineInterface::class => EngineMarkOne::class,
'engine_one' => EngineInterface::class,
]);
$container = new Container($config);
$object = $container->get('engine_one');
```
## Using class aliases for specific configuration
To define another instance of a class with specific configuration, you can
use native PHP `class_alias()`:
```php
class_alias(Yiisoft\Db\Pgsql\Connection::class, 'MyPgSql');
$config = ContainerConfig::create()
->withDefinitions([
MyPgSql::class => [ ... ]
]);
$container = new Container($config);
$object = $container->get(MyPgSql::class);
```
It could be then conveniently used by type-hinting:
```php
final class MyService
{
public function __construct(MyPgSql $myPgSql)
{
// ...
}
}
```
## Composite containers
A composite container combines many containers in a single container. When
using this approach, you should fetch objects only from the composite
container.
```php
use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
$composite = new CompositeContainer();
$carConfig = ContainerConfig::create()
->withDefinitions([
EngineInterface::class => EngineMarkOne::class,
CarInterface::class => Car::class
]);
$carContainer = new Container($carConfig);
$bikeConfig = ContainerConfig::create()
->withDefinitions([
BikeInterface::class => Bike::class
]);
$bikeContainer = new Container($bikeConfig);
$composite->attach($carContainer);
$composite->attach($bikeContainer);
// Returns an instance of a `Car` class.
$car = $composite->get(CarInterface::class);
// Returns an instance of a `Bike` class.
$bike = $composite->get(BikeInterface::class);
```
Note that containers attached earlier override dependencies of containers attached later.
```php
use Yiisoft\Di\CompositeContainer;
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
$carConfig = ContainerConfig::create()
->withDefinitions([
EngineInterface::class => EngineMarkOne::class,
CarInterface::class => Car::class
]);
$carContainer = new Container($carConfig);
$composite = new CompositeContainer();
$composite->attach($carContainer);
// Returns an instance of a `Car` class.
$car = $composite->get(CarInterface::class);
// Returns an instance of a `EngineMarkOne` class.
$engine = $car->getEngine();
$engineConfig = ContainerConfig::create()
->withDefinitions([
EngineInterface::class => EngineMarkTwo::class,
]);
$engineContainer = new Container($engineConfig);
$composite = new CompositeContainer();
$composite->attach($engineContainer);
$composite->attach($carContainer);
// Returns an instance of a `Car` class.
$car = $composite->get(CarInterface::class);
// Returns an instance of a `EngineMarkTwo` class.
$engine = $composite->get(EngineInterface::class);
```
## Using service providers
A service provider is a special class that's responsible for providing complex
services or groups of dependencies for the container and extensions of existing services.
A provider should extend from `Yiisoft\Di\ServiceProviderInterface` and must
contain a `getDefinitions()` and `getExtensions()` methods. It should only provide services for the container
and therefore should only contain code related to this task. It should *never*
implement any business logic or other functionality such as environment bootstrap or applying changes to a database.
A typical service provider could look like:
```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ServiceProviderInterface;
class CarFactoryProvider extends ServiceProviderInterface
{
public function getDefinitions(): array
{
return [
CarFactory::class => [
'class' => CarFactory::class,
'$color' => 'red',
],
EngineInterface::class => SolarEngine::class,
WheelInterface::class => [
'class' => Wheel::class,
'$color' => 'black',
],
CarInterface::class => [
'class' => BMW::class,
'$model' => 'X5',
],
];
}
public function getExtensions(): array
{
return [
// Note that Garage should already be defined in a container
Garage::class => function(ContainerInterface $container, Garage $garage) {
$car = $container
->get(CarFactory::class)
->create();
$garage->setCar($car);
return $garage;
}
];
}
}
```
Here you created a service provider responsible for bootstrapping of a car factory with all its dependencies.
An extension is callable that returns a modified service object.
In this case you get existing `Garage` service
and put a car into the garage by calling the method `setCar()`.
Thus, before applying this provider, you had
an empty garage and with the help of the extension you fill it.
To add this service provider to a container, you can pass either its class or a
configuration array in the extra config:
```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
$config = ContainerConfig::create()
->withProviders([CarFactoryProvider::class]);
$container = new Container($config);
```
When you add a service provider, DI calls its `getDefinitions()` and `getExtensions()` methods
*immediately* and both services and their extensions get registered into the container.
## Container tags
You can tag services in the following way:
```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
$config = ContainerConfig::create()
->withDefinitions([
BlueCarService::class => [
'class' => BlueCarService::class,
'tags' => ['car'],
],
RedCarService::class => [
'definition' => fn () => new RedCarService(),
'tags' => ['car'],
],
]);
$container = new Container($config);
```
Now you can get tagged services from the container in the following way:
```php
$container->get(\Yiisoft\Di\Reference\TagReference::to('car'));
```
The result is an array that has two instances: `BlueCarService` and `RedCarService`.
Another way to tag services is setting tags via container constructor:
```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
$config = ContainerConfig::create()
->withDefinitions([
BlueCarService::class => [
'class' => BlueCarService::class,
],
RedCarService::class => fn () => new RedCarService(),
])
->withTags([
// "car" tag has references to both blue and red cars
'car' => [BlueCarService::class, RedCarService::class]
]);
$container = new Container($config);
```
## Resetting services state
Despite stateful services isn't a great practice, these are often inevitable. When you build long-running
applications with tools like [Swoole](https://www.swoole.co.uk/) or [RoadRunner](https://roadrunner.dev/) you should
reset the state of such services every request. For this purpose you can use `StateResetter` with resetters callbacks:
```php
$resetter = new StateResetter($container);
$resetter->setResetters([
MyServiceInterface::class => function () {
$this->reset(); // a method of MyServiceInterface
},
]);
```
The callback has access to the private and protected properties of the service instance,
so you can set the initial state of the service efficiently without creating a new instance.
You should trigger the reset itself after each request-response cycle. For RoadRunner, it would look like the following:
```php
while ($request = $psr7->acceptRequest()) {
$response = $application->handle($request);
$psr7->respond($response);
$application->afterEmit($response);
$container
->get(\Yiisoft\Di\StateResetter::class)
->reset();
gc_collect_cycles();
}
```
### Setting resetters in definitions
You define the reset state for each service by providing "reset" callback in the following way:
```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
$config = ContainerConfig::create()
->withDefinitions([
EngineInterface::class => EngineMarkOne::class,
EngineMarkOne::class => [
'class' => EngineMarkOne::class,
'setNumber()' => [42],
'reset' => function () {
$this->number = 42;
},
],
]);
$container = new Container($config);
```
Note: resetters from definitions work only if you don't set `StateResetter` in definition or service providers.
### Configuring `StateResetter` manually
To manually add resetters or in case you use Yii DI composite container with a third party container that doesn't support state reset natively, you could configure state resetter separately. The following example is PHP-DI:
```php
MyServiceInterface::class => function () {
// ...
},
StateResetter::class => function (ContainerInterface $container) {
$resetter = new StateResetter($container);
$resetter->setResetters([
MyServiceInterface::class => function () {
$this->reset(); // a method of MyServiceInterface
},
]);
return $resetter;
}
```
## Specifying metadata for non-array definitions
To specify some metadata, such as in cases of "resetting services state" or "container tags," for non-array
definitions, you could use the following syntax:
```php
LogTarget::class => [
'definition' => static function (LoggerInterface $logger) use ($params) {
$target = ...
return $target;
},
'reset' => function () use ($params) {
...
},
],
```
Now you've explicitly moved the definition itself to "definition" key.
## Delegates
Each delegate is a callable returning a container instance that's used in case DI
can't find a service in a primary container:
```php
function (ContainerInterface $container): ContainerInterface
{
}
```
To configure delegates use extra config:
```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
$config = ContainerConfig::create()
->withDelegates([
function (ContainerInterface $container): ContainerInterface {
// ...
}
]);
$container = new Container($config);
```
## Tuning for production
By default, the container validates definitions right when they're set. In the production environment, it makes sense to
turn it off:
```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
$config = ContainerConfig::create()
->withValidate(false);
$container = new Container($config);
```
## Strict mode
Container may work in a strict mode, that's when you should define everything in the container explicitly.
To turn it on, use the following code:
```php
use Yiisoft\Di\Container;
use Yiisoft\Di\ContainerConfig;
$config = ContainerConfig::create()
->withStrictMode(true);
$container = new Container($config);
```
## Documentation
- [Internals](docs/internals.md)
If you need help or have a question, the [Yii Forum](https://forum.yiiframework.com/c/yii-3-0/63) is a good place for that.
You may also check out other [Yii Community Resources](https://www.yiiframework.com/community).
## License
The Yii Dependency Injection is free software. It is released under the terms of the BSD License.
Please see [`LICENSE`](./LICENSE.md) for more information.
Maintained by [Yii Software](https://www.yiiframework.com/).
## Support the project
[![Open Collective](https://img.shields.io/badge/Open%20Collective-sponsor-7eadf1?logo=open%20collective&logoColor=7eadf1&labelColor=555555)](https://opencollective.com/yiisoft)
## Follow updates
[![Official website](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=flat)](https://www.yiiframework.com/)
[![Twitter](https://img.shields.io/badge/twitter-follow-1DA1F2?logo=twitter&logoColor=1DA1F2&labelColor=555555?style=flat)](https://twitter.com/yiiframework)
[![Telegram](https://img.shields.io/badge/telegram-join-1DA1F2?style=flat&logo=telegram)](https://t.me/yii3en)
[![Facebook](https://img.shields.io/badge/facebook-join-1DA1F2?style=flat&logo=facebook&logoColor=ffffff)](https://www.facebook.com/groups/yiitalk)
[![Slack](https://img.shields.io/badge/slack-join-1DA1F2?style=flat&logo=slack)](https://yiiframework.com/go/slack)