From 8ac2fd0b69880323d3ba964aa6f1b36fa89e1eb8 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 16 Jan 2026 10:39:13 +0300 Subject: [PATCH 1/3] Mention yiisoft/factory and shared instances --- src/guide/concept/di-container.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/guide/concept/di-container.md b/src/guide/concept/di-container.md index 79fc4ee1..5cc6cc39 100644 --- a/src/guide/concept/di-container.md +++ b/src/guide/concept/di-container.md @@ -116,6 +116,9 @@ A dependency injection (DI) container is an object that knows how to instantiate all their dependent objects. [Martin Fowler's article](https://martinfowler.com/articles/injection.html) has well explained why DI container is useful. Here we will mainly explain the usage of the DI container provided by Yii. +> [!NOTE] +> The container contains only shared instances. If you need a factory, use the dedicated [yiisoft/factory](https://github.com/yiisoft/factory) package. + Yii provides the DI container feature through the [yiisoft/di](https://github.com/yiisoft/di) package and [yiisoft/injector](https://github.com/yiisoft/injector) package. From 2afd6aa54a9365525a2972278003e6825c068e8c Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 16 Jan 2026 11:24:19 +0300 Subject: [PATCH 2/3] Improve DI guide --- src/guide/concept/di-container.md | 133 +++++++++++++++++++++++------- 1 file changed, 105 insertions(+), 28 deletions(-) diff --git a/src/guide/concept/di-container.md b/src/guide/concept/di-container.md index 5cc6cc39..61b83d60 100644 --- a/src/guide/concept/di-container.md +++ b/src/guide/concept/di-container.md @@ -65,11 +65,14 @@ final readonly class CachedWidget } ``` -We've avoided unnecessary inheritance and used interface to reduce coupling. You can replace cache -implementation without changing `CachedWidget` so it's becoming more stable. +We've avoided unnecessary inheritance and used `CacheInterface` in the `CacheWidget` to reduce coupling. +You can replace cache implementation without changing `CachedWidget` so it's becoming more stable. The less +edits are made to the code, the less chance of breaking it. -The `CacheInterface` here is a dependency: an object another object depends on. -The process of putting an instance of dependency into an object (`CachedWidget`) is called dependency injection. +The `CacheInterface` here is a dependency: a contract our object needs to function. In another word, out object +depends on the contract. + +The process of putting an instance of a contract into an object (`CachedWidget`) is called dependency injection. There are many ways to perform it: - Constructor injection. Best for mandatory dependencies. @@ -107,21 +110,25 @@ requires lots of code and may lead to hardly debuggable mistakes. Additionally, lots of dependencies, such as certain third-party API wrappers, are the same for any class using it. So it makes sense to: -- Define how to instantiate such an API wrapper. -- Instantiate it when required and only once per request. +- Define how to instantiate such common dependencies. +- Instantiate them when required and only once per request. That's what dependency containers are for. A dependency injection (DI) container is an object that knows how to instantiate and configure objects and -all their dependent objects. [Martin Fowler's article](https://martinfowler.com/articles/injection.html) has well -explained why DI container is useful. Here we will mainly explain the usage of the DI container provided by Yii. - -> [!NOTE] -> The container contains only shared instances. If you need a factory, use the dedicated [yiisoft/factory](https://github.com/yiisoft/factory) package. +all objects they depend on. Yii provides the DI container feature through the [yiisoft/di](https://github.com/yiisoft/di) package and [yiisoft/injector](https://github.com/yiisoft/injector) package. +> [!NOTE] +> The container contains only shared instances. If you need a factory, use the dedicated +> [yiisoft/factory](https://github.com/yiisoft/factory) package. + +> [!TIP] +> [Martin Fowler's article](https://martinfowler.com/articles/injection.html) has well +> explained why DI container is useful. Here we will mainly explain the usage of the DI container provided by Yii. + ### Configuring container Because to create a new object you need its dependencies, you should register them as early as possible. @@ -160,35 +167,105 @@ $myService = new MyService(42); $myService->setDiscount(10); ``` -There are extra methods of declaring dependencies: +You can provide arguments with names as well: + +```php +return [ + MyServiceInterface::class => [ + 'class' => MyService::class, + '__construct()' => ['amount' => 42], + 'setDiscount()' => ['discount' => 10], + ], +]; +``` + +That's basically it. You define a map of interfaces to classes and define how to configure them. When an interface +is requested in constructor or elsewhere, container creates an instance of a class and configures it as per the configuration: + +```php +final class MyAction +{ + public function __construct( + private readonly MyServiceInterface $myService + ) { + } + + public function __invoke() + { + $this->myService->doSomething(); + } +} +``` + +There are extra methods of declaring dependency configuration. + +For simplest cases where there are no custom values needed and all the constructor dependencies could be obtained +from a container, you can use a class name as a value. + +```php +interface EngineInterface +{ + +} + +final class EngineMarkOne implements EngineInterface +{ + public function __construct(CacheInterface $cache) { + } +} +``` + +In the above example, if we already have cache defined in the container, nothing besides the class name is needed: ```php return [ // declare a class for an interface, resolve dependencies automatically EngineInterface::class => EngineMarkOne::class, +]; +``` - // array definition (same as above) - 'full_definition' => [ - 'class' => EngineMarkOne::class, - '__construct()' => [42], - '$propertyName' => 'value', - 'setX()' => [42], - ], +If you have a dependency that has public properties, you can configure it as well. + + +```php +final class NameProvider +{ + public string $name; +} +``` + +Here's how to do it for the example above: + +```php +NameProvider::class => [ + 'class' => NameProvider::class, + '$name' => 'Alex', +], +``` - // closure - 'closure' => static function(ContainerInterface $container) { - return new MyClass($container->get('db')); - }, +In this example, you may notice `NameProvider` specified twice. The key is what you may request as dependency and the +value is how to create it. - // static call - 'static_call' => [MyFactory::class, 'create'], +If the configuration is tricky and requires some logic, a closure can be used: + +```php +MyServiceInterface::class => static function(ContainerInterface $container) { + return new MyService($container->get('db')); +}, +``` + +As an argument, a container is passed to a closure. It can be used to resolve dependencies. + +It's possible to use a static method call or an instance of an object as well: + +```php +MyServiceInterface::class => [MyFactory::class, 'create'], - // instance of an object - 'object' => new MyClass(), +MyServiceInterface::class => new MyService(), ]; ``` -### Injecting dependencies +### Injecting dependencies properly Directly referencing a container in a class is a bad idea since the code becomes non-generic, coupled to the container interface and, what's worse, dependencies are becoming hidden. From 051a426b0a67a07e1fcc77ba8fdf686f86b4715d Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 16 Jan 2026 13:35:45 +0300 Subject: [PATCH 3/3] Review fixes --- src/guide/concept/di-container.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/guide/concept/di-container.md b/src/guide/concept/di-container.md index 61b83d60..9926181e 100644 --- a/src/guide/concept/di-container.md +++ b/src/guide/concept/di-container.md @@ -69,7 +69,7 @@ We've avoided unnecessary inheritance and used `CacheInterface` in the `CacheWid You can replace cache implementation without changing `CachedWidget` so it's becoming more stable. The less edits are made to the code, the less chance of breaking it. -The `CacheInterface` here is a dependency: a contract our object needs to function. In another word, out object +The `CacheInterface` here is a dependency: a contract our object needs to function. In other words, our object depends on the contract. The process of putting an instance of a contract into an object (`CachedWidget`) is called dependency injection. @@ -256,16 +256,19 @@ MyServiceInterface::class => static function(ContainerInterface $container) { As an argument, a container is passed to a closure. It can be used to resolve dependencies. -It's possible to use a static method call or an instance of an object as well: +It's possible to use a static method call: ```php MyServiceInterface::class => [MyFactory::class, 'create'], +``` + +Or an instance of an object: +```php MyServiceInterface::class => new MyService(), -]; ``` -### Injecting dependencies properly +### Injecting dependencies properly Directly referencing a container in a class is a bad idea since the code becomes non-generic, coupled to the container interface and, what's worse, dependencies are becoming hidden.