diff --git a/docs/guide-ru/concept-configurations.md b/docs/guide-ru/concept-configurations.md index c0ac69b7c4..2a755380c7 100644 --- a/docs/guide-ru/concept-configurations.md +++ b/docs/guide-ru/concept-configurations.md @@ -135,6 +135,29 @@ $config = [ За более подробной документацией о настройках свойства `components` в конфигурации приложения обратитесь к главам [приложения](structure-applications.md) и [Service Locator](concept-service-locator.md). +Начиная с версии 2.0.11, можно настраивать [контейнер зависимостей](concept-di-container.md) через конфигурацию +приложения. Для этого используется свойство `container`: + +```php +$config = [ + 'id' => 'basic', + 'basePath' => dirname(__DIR__), + 'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'), + 'container' => [ + 'definitions' => [ + 'yii\widgets\LinkPager' => ['maxButtonCount' => 5] + ], + 'singletons' => [ + // Конфигурация для единожды создающихся объектов + ] + ] +]; +``` + +Чтобы узнать о возможных значениях `definitions` и `singletons`, а также о реальных примерах использования, +прочитайте подраздел [более сложное практическое применение](concept-di-container.md#advanced-practical-usage) раздела +[Dependency Injection Container](concept-di-container.md). + ### Конфигурации виджетов diff --git a/docs/guide-ru/concept-di-container.md b/docs/guide-ru/concept-di-container.md index 0ede428088..c9864f23fc 100644 --- a/docs/guide-ru/concept-di-container.md +++ b/docs/guide-ru/concept-di-container.md @@ -104,6 +104,138 @@ $container->get('Foo', [], [ > Info: Метод [[yii\di\Container::get()]] третьим аргументом принимает массив конфигурации, которым инициализируется создаваемый объект. Если класс реализует интерфейс [[yii\base\Configurable]] (например, [[yii\base\Object]]), то массив конфигурации передается в последний параметр конструктора класса. Иначе конфигурация применяется уже *после* создания объекта. +Более сложное практическое применение +--------------- + +Допустим, мы работаем над API и у нас есть: + +- `app\components\Request`, наследуемый от `yii\web\Request` и реализующий дополнительные возможности. +- `app\components\Response`, наследуемый от `yii\web\Response` с свойством `format`, по умолчанию инициализируемом как `json`. +- `app\storage\FileStorage` и `app\storage\DocumentsReader`, где реализована некая логика для работы с документами в + неком файловом хранилище: + + ```php + class FileStorage + { + public function __contruct($root) { + // делаем что-то + } + } + + class DocumentsReader + { + public function __contruct(FileStorage $fs) { + // делаем что-то + } + } + ``` + +Возможно настроить несколько компонентов сразу передав массив конфигурации в метод +[[yii\di\Container::setDefinitions()|setDefinitions()]] или [[yii\di\Container::setSingletons()|setSingletons()]]. +Внутри метода фреймворк обойдёт массив конфигурации и вызовет для каждого элемента [[yii\di\Container::set()|set()]] или +[[yii\di\Container::setSingleton()|setSingleton()]] соответственно. + +Формат массива конфигурации следующий: + + - Ключ: имя класса, интерфейса или псевдонима. Ключ передаётся в первый аргумент `$class` метода + [[yii\di\Container::set()|set()]]. + - Значение: конфигурация для класса. Возможные значения описаны в документации параметра `$definition` метода + [[yii\di\Container::set()|set()]]. Значение передаётся в аргумент `$definition` метода [[set()]]. + +Для примера, давайте настроим наш контейнер: + +```php +$container->setDefinitions([ + 'yii\web\Request' => 'app\components\Request', + 'yii\web\Response' => [ + 'class' => 'app\components\Response', + 'format' => 'json' + ], + 'app\storage\DocumentsReader' => function () { + $fs = new app\storage\FileStorage('/var/tempfiles'); + return new app\storage\DocumentsReader($fs); + } +]); + +$reader = $container->get('app\storage\DocumentsReader); +// Создаст объект DocumentReader со всеми зависимостями +``` + +> Tip: Начиная с версии 2.0.11 контейнер может быть настроен в декларативном стиле через конфигурацию приложения. +Как это сделать ищите в подразделе [Конфигурация приложения](concept-service-locator.md#application-configurations) +раздела [Конфигурации](concept-configurations.md). + +Вроде всё работает, но если нам необходимо создать экземпляр класса `DocumentWriter`, придётся скопировать код, +создающий экземпляр`FileStorage`, что, очевидно, не является оптимальным. + +Как описано в подразделе [Разрешение зависимостей](#resolving-dependencies), [[yii\di\Container::set()|set()]] +и [[yii\di\Container::setSingleton()|setSingleton()]] могут опционально принимать третьим аргументов параметры +для конструктора. Формат таков: + + - Ключ: имя класса, интерфейса или псевдонима. Ключ передаётся в первый аргумент `$class` метода [[yii\di\Container::set()|set()]]. + - Значение: массив из двух элементов. Первый элемент передаётся в метод [[yii\di\Container::set()|set()]] вторым + аргументом `$definition`, второй элемент — аргументом `$params`. + +Исправим наш пример: + +```php +$container->setDefinitions([ + 'tempFileStorage' => [ // для удобства мы задали псевдоним + ['class' => 'app\storage\FileStorage'], + ['/var/tempfiles'] + ], + 'app\storage\DocumentsReader' => [ + ['class' => 'app\storage\DocumentsReader'], + [Instance::of('tempFileStorage')] + ], + 'app\storage\DocumentsWriter' => [ + ['class' => 'app\storage\DocumentsWriter'], + [Instance::of('tempFileStorage')] + ] +]); + +$reader = $container->get('app\storage\DocumentsReader); +// Код будет работать ровно так же, как и в предыдущем примере. +``` + +Вы могли заметить вызов `Instance::of('tempFileStorage')`. Он означает, что [[yii\di\Container|Container]] +наявно предоставит зависимость, зарегистрированную с именем `tempFileStorage` и передаст её первым аргументом +в конструктор `app\storage\DocumentsWriter`. + +> Note: Методы [[yii\di\Container::setDefinitions()|setDefinitions()]] и [[yii\di\Container::setSingletons()|setSingletons()]] + доступны с версии 2.0.11. + +Ещё один шаг по оптимизации конфигурации — регистрировать некоторые зависимости как синглтоны. Зависимость, регистрируемая +через метод [[yii\di\Container::set()|set()]] будет созаваться каждый раз при обращении к ней. Некоторые классы не меняют +своего состояния на протяжении всей работы приложения, поэтому могут быть зарегистрированы как синглтоны. Это увеличит +производительность приложения. + +Хорошим примером может быть класс `app\storage\FileStorage`, который выполняет некие операции над файловой системой +через простой API: `$fs->read()`, `$fs->write()`. Обе операции не меняют внутреннее состояние класса, поэтому мы можем +создать класс один раз и далее использовать его. + +```php +$container->setSingletons([ + 'tempFileStorage' => [ + ['class' => 'app\storage\FileStorage'], + ['/var/tempfiles'] + ], +]); + +$container->setDefinitions([ + 'app\storage\DocumentsReader' => [ + ['class' => 'app\storage\DocumentsReader'], + [Instance::of('tempFileStorage')] + ], + 'app\storage\DocumentsWriter' => [ + ['class' => 'app\storage\DocumentsWriter'], + [Instance::of('tempFileStorage')] + ] +]); + +$reader = $container->get('app\storage\DocumentsReader); +``` + ### Внедрение зависимости через PHP callback В данном случае, контейнер будет использовать зарегистрированный PHP callback для создания новых экземпляров класса. @@ -211,13 +343,16 @@ $container->setSingleton('yii\db\Connection', [ Разрешение зависимостей ---------------------- После регистрации зависимостей, вы можете использовать контейнер внедрения зависимостей для создания новых объектов, -и контейнер автоматически разрешит зависимости их экземпляра и их внедрений во вновь создаваемых объектах. Разрешение зависимостей рекурсивно, то есть -если зависимость имеет другие зависимости, эти зависимости также будут автоматически разрешены. +и контейнер автоматически разрешит зависимости их экземпляра и их внедрений во вновь создаваемых объектах. Разрешение +зависимостей рекурсивно, то есть если зависимость имеет другие зависимости, эти зависимости также будут автоматически +разрешены. -Вы можете использовать [[yii\di\Container::get()]] для создания новых объектов. Метод принимает имя зависимости, которым может быть имя класса, имя интерфейса или псевдоним. -Имя зависимости может быть или не может быть зарегистрировано через `set()` или `setSingleton()`. -Вы можете опционально предоставить список параметров конструктора класса и [конфигурацию](concept-configurations.md) для настройки созданного объекта. -Например, +Вы можете использовать [[yii\di\Container::get()]] для создания или получения объектов. Метод принимает имя зависимости, +которым может быть имя класса, имя интерфейса или псевдоним. Имя зависимости может быть зарегистрировано через +`set()` или `setSingleton()`. Вы можете опционально предоставить список параметров конструктора класса и +[конфигурацию](concept-configurations.md) для настройки созданного объекта. + +Например: ```php // "db" ранее зарегистрированный псевдоним @@ -228,11 +363,14 @@ $engine = $container->get('app\components\SearchEngine', [$apiKey], ['type' => 1 ``` За кулисами, контейнер внедрения зависимостей делает гораздо больше работы, чем просто создание нового объекта. -Прежде всего, контейнер, осмотрит конструктор класса, чтобы узнать имя зависимого класса или интерфейса, а затем автоматически разрешит эти зависимости рекурсивно. +Прежде всего, контейнер, осмотрит конструктор класса, чтобы узнать имя зависимого класса или интерфейса, а затем +автоматически разрешит эти зависимости рекурсивно. -Следующий код демонстрирует более сложный пример. Класс `UserLister` зависит от объекта, реализующего интерфейс `UserFinderInterface`; класс `UserFinder` реализует этот интерфейс и зависит от - объекта `Connection`. Все эти зависимости были объявлены через тип подсказки параметров конструктора класса. -При регистрации зависимости через свойство, контейнер внедрения зависимостей позволяет автоматически разрешить эти зависимости и создаёт новый экземпляр `UserLister` простым вызовом `get('userLister')`. +Следующий код демонстрирует более сложный пример. Класс `UserLister` зависит от объекта, реализующего интерфейс +`UserFinderInterface`; класс `UserFinder` реализует этот интерфейс и зависит от объекта `Connection`. Все эти зависимости +были объявлены через тип подсказки параметров конструктора класса. При регистрации зависимости через свойство, контейнер +внедрения зависимостей позволяет автоматически разрешить эти зависимости и создаёт новый экземпляр `UserLister` простым +вызовом `get('userLister')`. ```php namespace app\models; @@ -291,17 +429,17 @@ $lister = new UserLister($finder); ``` -Практическое использование +Практическое применение --------------- Yii создаёт контейнер внедрения зависимостей когда вы подключаете файл `Yii.php` во [входном скрипте](structure-entry-scripts.md) вашего приложения. Контейнер внедрения зависимостей доступен через [[Yii::$container]]. При вызове [[Yii::createObject()]], метод на самом деле вызовет метод контейнера [[yii\di\Container::get()|get()]], чтобы создать новый объект. -Как упомянуто выше, контейнер внедрения зависимостей автоматически разрешит зависимости (если таковые имеются) и внедрит их в только что созданный объект. -Поскольку Yii использует [[Yii::createObject()]] в большей части кода своего ядра для создания новых объектов, это означает, -что вы можете настроить глобальные объекты, имея дело с [[Yii::$container]]. +Как упомянуто выше, контейнер внедрения зависимостей автоматически разрешит зависимости (если таковые имеются) и внедрит их +получаемый объект. Поскольку Yii использует [[Yii::createObject()]] в большей части кода своего ядра для создания новых +объектов, это означает, что вы можете настроить глобальные объекты, имея дело с [[Yii::$container]]. -Например, вы можете настроить по умолчанию глобальное количество кнопок в пейджере [[yii\widgets\LinkPager]]: +Например, давайте настроим количество кнопок в пейджере [[yii\widgets\LinkPager]] по умолчанию глобально: ```php \Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]); @@ -356,8 +494,11 @@ class HotelController extends Controller Поскольку зависимости необходимы тогда, когда создаются новые объекты, то их регистрация должна быть сделана как можно раньше. Ниже приведены рекомендуемые практики: -* Если вы разработчик приложения, то вы можете зарегистрировать зависимости во [входном скрипте](structure-entry-scripts.md) вашего приложения или в скрипте, подключённого во входном скрипте. -* Если вы разработчик распространяемого [расширения](structure-extensions.md), то вы можете зарегистрировать зависимости в загрузочном классе расширения. +* Если вы разработчик приложения, то вы можете зарегистрировать зависимости в конфигурации вашего приложения. + Как это сделать описано в подразделе [Конфигурация приложения](concept-service-locator.md#application-configurations) + раздела [Конфигурации](concept-configurations.md). +* Если вы разработчик распространяемого [расширения](structure-extensions.md), то вы можете зарегистрировать зависимости + в загрузочном классе расширения. Итог