concept-di-container.md 17.9 KB
Newer Older
1
Контейнер внедрения зависимостей
quot;brussens committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
==============================

Контейнер внедрения зависимостей - это объект, который знает, как создать и настроить экземпляр объекта и зависимых от него объектов.
[Статья Мартина Фаулера](http://martinfowler.com/articles/injection.html) хорошо объясняет, почему контейнер внедрения зависимостей является полезным. Здесь, преимущественно, будет объясняться использование контейнера внедрения зависимостей, предоставляемого в Yii.




Внедрение зависимостей <a name="dependency-injection"></a>
--------------------

Yii обеспечивает функционал контейнера внедрения зависимостей через класс [[yii\di\Container]]. Он поддерживает следующие виды внедрения зависимостей:

* Внедрение зависимости через конструктор.
* Внедрение зависимости через сеттер и свойство.
quot;brussens committed
17
* Внедрение зависимости через PHP callback.
quot;brussens committed
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43


### Внедрение зависимости через конструктор <a name="constructor-injection"></a>

Контейнер внедрения зависимостей поддерживает внедрение зависимости через конструктор при помощи указания типов для параметров конструктора.
Указанные типы сообщают контейнеру, какие классы или интерфейсы зависят от него при создании нового объекта.
Контейнер попытается получить экземпляры зависимых классов или интерфейсов, а затем передать их в новый объект через конструктор. Например,

```php
class Foo
{
    public function __construct(Bar $bar)
    {
    }
}

$foo = $container->get('Foo');
// что равносильно следующему:
$bar = new Bar;
$foo = new Foo($bar);
```


### Внедрение зависимости через сеттер и свойство <a name="setter-and-property-injection"></a>

Внедрение зависимости через сеттер и свойство поддерживается через [конфигурации](concept-configurations.md).
quot;brussens committed
44 45 46
При регистрации зависимости или при создании нового объекта, вы можете предоставить конфигурацию, которая
будет использована контейнером для внедрения зависимостей через соответствующие сеттеры или свойства.
Например,
quot;brussens committed
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

```php
use yii\base\Object;

class Foo extends Object
{
    public $bar;

    private $_qux;

    public function getQux()
    {
        return $this->_qux;
    }

    public function setQux(Qux $qux)
    {
        $this->_qux = $qux;
    }
}

$container->get('Foo', [], [
    'bar' => $container->get('Bar'),
    'qux' => $container->get('Qux'),
]);
```


quot;brussens committed
75
### Внедрение зависимости через PHP callback <a name="php-callable-injection"></a>
quot;brussens committed
76

quot;brussens committed
77 78
В данном случае, контейнер будет использовать зарегистрированный PHP callback для создания новых экземпляров класса.
Callback отвечает за разрешения зависимостей и внедряет их в соответствии с вновь создаваемыми объектами. Например,
quot;brussens committed
79 80 81 82 83 84 85 86 87 88

```php
$container->set('Foo', function () {
    return new Foo(new Bar);
});

$foo = $container->get('Foo');
```


quot;brussens committed
89
Регистрация зависимостей <a name="registering-dependencies"></a>
quot;brussens committed
90 91
------------------------

quot;brussens committed
92
Вы можете использовать [[yii\di\Container::set()]] для регистрации зависимостей. При регистрации требуется имя зависимости, а так же определение зависимости. 
93
Именем зависимости может быть имя класса, интерфейса или алиас, так же определением зависимости может быть имя класса, конфигурационным массивом, или PHP calback'ом.
quot;brussens committed
94 95 96 97

```php
$container = new \yii\di\Container;

quot;brussens committed
98
// регистрация имени класса, как есть. это может быть пропущено.
quot;brussens committed
99 100
$container->set('yii\db\Connection');

101
// регистрация интерфейса
quot;brussens committed
102 103
// Когда класс зависит от интерфейса, соответствующий класс
// будет использован в качестве зависимости объекта
quot;brussens committed
104 105
$container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer');

quot;brussens committed
106 107
// регистрация алиаса. Вы можете использовать $container->get('foo')
// для создания экземпляра Connection
quot;brussens committed
108 109
$container->set('foo', 'yii\db\Connection');

quot;brussens committed
110 111
// Регистрация класса с конфигурацией. Конфигурация
// будет применена при создании экземпляра класса через get()
quot;brussens committed
112 113 114 115 116 117 118
$container->set('yii\db\Connection', [
    'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
    'username' => 'root',
    'password' => '',
    'charset' => 'utf8',
]);

quot;brussens committed
119 120
// регистрация алиаса с конфигурацией класса
// В данном случае, параметр "class" требуется для указания класса
quot;brussens committed
121 122 123 124 125 126 127 128
$container->set('db', [
    'class' => 'yii\db\Connection',
    'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
    'username' => 'root',
    'password' => '',
    'charset' => 'utf8',
]);

quot;brussens committed
129 130
// регистрация PHP callback'a
// Callback будет выполняться каждый раз при вызове $container->get('db')
quot;brussens committed
131 132 133 134
$container->set('db', function ($container, $params, $config) {
    return new \yii\db\Connection($config);
});

quot;brussens committed
135 136
// регистрация экземпляра компонента
// $container->get('pageCache') вернёт тот же экземпляр при каждом вызове
quot;brussens committed
137 138 139
$container->set('pageCache', new FileCache);
```

quot;brussens committed
140
> Подсказка: Если имя зависимости такое же, как и определение соответствующей зависимости, то её повторная регистрация в контейнере внедрения зависимостей не нужна.
quot;brussens committed
141

quot;brussens committed
142 143
Зависимость, зарегистрированная через `set()` создаёт экземпляр каждый раз, когда зависимость необходима.
Вы можете использовать [[yii\di\Container::setSingleton()]] для регистрации зависимости, которая создаст только один экземпляр:
quot;brussens committed
144 145 146 147 148 149 150 151 152 153 154

```php
$container->setSingleton('yii\db\Connection', [
    'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
    'username' => 'root',
    'password' => '',
    'charset' => 'utf8',
]);
```


155
Разрешение зависимостей <a name="resolving-dependencies"></a>
quot;brussens committed
156
----------------------
157
После регистрации зависимостей, вы можете использовать контейнер внедрения зависимостей для создания новых объектов,
quot;brussens committed
158 159
и контейнер автоматически разрешит зависимости их экземпляра и их внедрений во вновь создаваемых объектах. Разрешение зависимостей рекурсивно, то есть
если зависимость имеет другие зависимости, эти зависимости также будут автоматически разрешены.
quot;brussens committed
160

quot;brussens committed
161
Вы можете использовать [[yii\di\Container::get()]] для создания новых объектов. Метод принимает имя зависимости, которым может быть имя класса, имя интерфейса или псевдоним.
quot;brussens committed
162
Имя зависимости может быть или не может быть зарегистрировано через `set()` или `setSingleton()`. 
quot;brussens committed
163 164
Вы можете опционально предоставить список параметров конструктора класса и [конфигурацию](concept-configurations.md) для настройки созданного объекта.
Например,
quot;brussens committed
165 166

```php
quot;brussens committed
167
// "db" ранее зарегистрированный псевдоним
quot;brussens committed
168 169
$db = $container->get('db');

quot;brussens committed
170
// эквивалентно: $engine = new \app\components\SearchEngine($apiKey, ['type' => 1]);
quot;brussens committed
171 172 173
$engine = $container->get('app\components\SearchEngine', [$apiKey], ['type' => 1]);
```

174
За кулисами, контейнер внедрения зависимостей делает гораздо больше работы, чем просто создание нового объекта.
175
Прежде всего, контейнер, осмотрит конструктор класса, чтобы узнать имя зависимого класса или интерфейса, а затем автоматически разрешит эти зависимости рекурсивно.
quot;brussens committed
176

quot;brussens committed
177 178
Следующий код демонстрирует более сложный пример. Класс `UserLister` зависит от объекта, реализующего интерфейс `UserFinderInterface`; класс `UserFinder` реализует этот интерфейс и зависит от
 объекта `Connection`. Все эти зависимости были объявлены через тип подсказки параметров конструктора класса.
179
При регистрации зависимости через свойство, контейнер внедрения зависимостей позволяет автоматически разрешить эти зависимости и создаёт новый экземпляр `UserLister` простым вызовом `get('userLister')`.
quot;brussens committed
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229

```php
namespace app\models;

use yii\base\Object;
use yii\db\Connection;
use yii\di\Container;

interface UserFinderInterface
{
    function findUser();
}

class UserFinder extends Object implements UserFinderInterface
{
    public $db;

    public function __construct(Connection $db, $config = [])
    {
        $this->db = $db;
        parent::__construct($config);
    }

    public function findUser()
    {
    }
}

class UserLister extends Object
{
    public $finder;

    public function __construct(UserFinderInterface $finder, $config = [])
    {
        $this->finder = $finder;
        parent::__construct($config);
    }
}

$container = new Container;
$container->set('yii\db\Connection', [
    'dsn' => '...',
]);
$container->set('app\models\UserFinderInterface', [
    'class' => 'app\models\UserFinder',
]);
$container->set('userLister', 'app\models\UserLister');

$lister = $container->get('userLister');

quot;brussens committed
230
// что эквивалентно:
quot;brussens committed
231 232 233 234 235 236 237 238 239 240

$db = new \yii\db\Connection(['dsn' => '...']);
$finder = new UserFinder($db);
$lister = new UserLister($finder);
```


Практическое использование <a name="practical-usage"></a>
---------------

241 242
Yii создаёт контейнер внедрения зависимостей когда вы подключаете файл `Yii.php` во [входном скрипте](structure-entry-scripts.md)
вашего приложения. Контейнер внедрения зависимостей доступен через [[Yii::$container]]. При вызове [[Yii::createObject()]],
243
метод на самом деле вызовет метод контейнера [[yii\di\Container::get()|get()]], чтобы создать новый объект.
244
Как упомянуто выше, контейнер внедрения зависимостей автоматически разрешит зависимости (если таковые имеются) и внедрит их в только что созданный объект.
245 246
Поскольку Yii использует [[Yii::createObject()]] в большей части кода своего ядра для создания новых объектов, это означает,
что вы можете настроить глобальные объекты, имея дело с [[Yii::$container]].
quot;brussens committed
247

248
Например, вы можете настроить по умолчанию глобальное количество кнопок в пейджере [[yii\widgets\LinkPager]]:
quot;brussens committed
249 250 251 252 253

```php
\Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]);
```

254
Теперь, если вы вызовете в представлении виджет, используя следующий код, то свойство `maxButtonCount` будет инициализировано, как 5, вместо значения по умолчанию 10, как это определено в классе.
quot;brussens committed
255 256 257 258 259

```php
echo \yii\widgets\LinkPager::widget();
```

260
Хотя, вы всё ещё можете переопределить установленное значение через контейнер внедрения зависимостей:
quot;brussens committed
261 262 263 264

```php
echo \yii\widgets\LinkPager::widget(['maxButtonCount' => 20]);
```
265
Другим примером является использование автоматического внедрения зависимости через конструктор контейнера внедрения зависимостей.
266
Предположим, ваш класс контроллера зависит от ряда других объектов, таких как сервис бронирования гостиницы. Вы
267
можете объявить зависимость через параметр конструктора и позволить контейнеру внедрения зависимостей, разрешить её за вас.
quot;brussens committed
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286

```php
namespace app\controllers;

use yii\web\Controller;
use app\components\BookingInterface;

class HotelController extends Controller
{
    protected $bookingService;

    public function __construct($id, $module, BookingInterface $bookingService, $config = [])
    {
        $this->bookingService = $bookingService;
        parent::__construct($id, $module, $config);
    }
}
```

287
Если у вас есть доступ к этому контроллеру из браузера, вы увидите сообщение об ошибке, который жалуется на то, что `BookingInterface`
288
не может быть создан. Это потому что вы должны указать контейнеру внедрения зависимостей, как обращаться с этой зависимостью:
quot;brussens committed
289 290 291 292 293

```php
\Yii::$container->set('app\components\BookingInterface', 'app\components\BookingService');
```

294
Теперь, если вы попытаетесь получить доступ к контроллеру снова, то экземпляр `app\components\BookingService` будет создан и введён в качестве 3-го параметра конструктора контроллера.
quot;brussens committed
295 296


297
Когда следует регистрировать зависимости <a name="when-to-register-dependencies"></a>
quot;brussens committed
298 299
-----------------------------

300 301
Поскольку зависимости необходимы тогда, когда создаются новые объекты, то их регистрация должна быть сделана
как можно раньше. Ниже приведены рекомендуемые практики:
quot;brussens committed
302

303 304
* Если вы разработчик приложения, то вы можете зарегистрировать зависимости во [входном скрипте](structure-entry-scripts.md) вашего приложения или в скрипте, подключённого во входном скрипте.
* Если вы разработчик распространяемого [расширения](structure-extensions.md), то вы можете зарегистрировать зависимости в загрузочном классе расширения.
quot;brussens committed
305 306


307
Итог <a name="summary"></a>
quot;brussens committed
308
-------
309
Как dependency injection, так и [service locator](concept-service-locator.md) являются популярными паттернами проектирования, которые позволяют 
310
создавать программное обеспечение в слабосвязанной и более тестируемой манере.
311
Мы настоятельно рекомендуем к прочтению
quot;brussens committed
312
[статью Мартина Фаулера](http://martinfowler.com/articles/injection.html), для более глубокого понимания dependency injection и service locator. 
313

314
Yii реализует свой [service locator](concept-service-locator.md) поверх контейнера внедрения зависимостей.
quot;brussens committed
315
Когда service locator пытается создать новый экземпляр объекта, он перенаправляет вызов на контейнер внедрения зависимостей.
316
Последний будет разрешать зависимости автоматически, как описано выше.
quot;brussens committed
317