Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/yiisoft/di

PSR-11 compatible DI container and injector
https://github.com/yiisoft/di

autowiring container dependency-injection di di-container hacktoberfest injector psr-11 yii3

Last synced: about 1 month ago
JSON representation

PSR-11 compatible DI container and injector

Awesome Lists containing this project

README

        



Yii

Yii Dependency Injection



[![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');
```

## 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)