mirror of
https://github.com/yiisoft/yii2.git
synced 2025-11-03 13:58:55 +08:00
318 lines
18 KiB
Markdown
318 lines
18 KiB
Markdown
Контейнер внедрения зависимостей
|
||
==============================
|
||
|
||
Контейнер внедрения зависимостей - это объект, который знает, как создать и настроить экземпляр объекта и зависимых от него объектов.
|
||
[Статья Мартина Фаулера](http://martinfowler.com/articles/injection.html) хорошо объясняет, почему контейнер внедрения зависимостей является полезным. Здесь, преимущественно, будет объясняться использование контейнера внедрения зависимостей, предоставляемого в Yii.
|
||
|
||
|
||
|
||
|
||
Внедрение зависимостей <span id="dependency-injection"></span>
|
||
--------------------
|
||
|
||
Yii обеспечивает функционал контейнера внедрения зависимостей через класс [[yii\di\Container]]. Он поддерживает следующие виды внедрения зависимостей:
|
||
|
||
* Внедрение зависимости через конструктор.
|
||
* Внедрение зависимости через сеттер и свойство.
|
||
* Внедрение зависимости через PHP callback.
|
||
|
||
|
||
### Внедрение зависимости через конструктор <span id="constructor-injection"></span>
|
||
|
||
Контейнер внедрения зависимостей поддерживает внедрение зависимости через конструктор при помощи указания типов для параметров конструктора.
|
||
Указанные типы сообщают контейнеру, какие классы или интерфейсы зависят от него при создании нового объекта.
|
||
Контейнер попытается получить экземпляры зависимых классов или интерфейсов, а затем передать их в новый объект через конструктор. Например,
|
||
|
||
```php
|
||
class Foo
|
||
{
|
||
public function __construct(Bar $bar)
|
||
{
|
||
}
|
||
}
|
||
|
||
$foo = $container->get('Foo');
|
||
// что равносильно следующему:
|
||
$bar = new Bar;
|
||
$foo = new Foo($bar);
|
||
```
|
||
|
||
|
||
### Внедрение зависимости через сеттер и свойство <span id="setter-and-property-injection"></span>
|
||
|
||
Внедрение зависимости через сеттер и свойство поддерживается через [конфигурации](concept-configurations.md).
|
||
При регистрации зависимости или при создании нового объекта, вы можете предоставить конфигурацию, которая
|
||
будет использована контейнером для внедрения зависимостей через соответствующие сеттеры или свойства.
|
||
Например,
|
||
|
||
```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'),
|
||
]);
|
||
```
|
||
|
||
|
||
### Внедрение зависимости через PHP callback <span id="php-callable-injection"></span>
|
||
|
||
В данном случае, контейнер будет использовать зарегистрированный PHP callback для создания новых экземпляров класса.
|
||
Callback отвечает за разрешения зависимостей и внедряет их в соответствии с вновь создаваемыми объектами. Например,
|
||
|
||
```php
|
||
$container->set('Foo', function () {
|
||
return new Foo(new Bar);
|
||
});
|
||
|
||
$foo = $container->get('Foo');
|
||
```
|
||
|
||
|
||
Регистрация зависимостей <span id="registering-dependencies"></span>
|
||
------------------------
|
||
|
||
Вы можете использовать [[yii\di\Container::set()]] для регистрации зависимостей. При регистрации требуется имя зависимости, а так же определение зависимости.
|
||
Именем зависимости может быть имя класса, интерфейса или алиас, так же определением зависимости может быть имя класса, конфигурационным массивом, или PHP calback'ом.
|
||
|
||
```php
|
||
$container = new \yii\di\Container;
|
||
|
||
// регистрация имени класса, как есть. это может быть пропущено.
|
||
$container->set('yii\db\Connection');
|
||
|
||
// регистрация интерфейса
|
||
// Когда класс зависит от интерфейса, соответствующий класс
|
||
// будет использован в качестве зависимости объекта
|
||
$container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer');
|
||
|
||
// регистрация алиаса. Вы можете использовать $container->get('foo')
|
||
// для создания экземпляра Connection
|
||
$container->set('foo', 'yii\db\Connection');
|
||
|
||
// Регистрация класса с конфигурацией. Конфигурация
|
||
// будет применена при создании экземпляра класса через get()
|
||
$container->set('yii\db\Connection', [
|
||
'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
|
||
'username' => 'root',
|
||
'password' => '',
|
||
'charset' => 'utf8',
|
||
]);
|
||
|
||
// регистрация алиаса с конфигурацией класса
|
||
// В данном случае, параметр "class" требуется для указания класса
|
||
$container->set('db', [
|
||
'class' => 'yii\db\Connection',
|
||
'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
|
||
'username' => 'root',
|
||
'password' => '',
|
||
'charset' => 'utf8',
|
||
]);
|
||
|
||
// регистрация PHP callback'a
|
||
// Callback будет выполняться каждый раз при вызове $container->get('db')
|
||
$container->set('db', function ($container, $params, $config) {
|
||
return new \yii\db\Connection($config);
|
||
});
|
||
|
||
// регистрация экземпляра компонента
|
||
// $container->get('pageCache') вернёт тот же экземпляр при каждом вызове
|
||
$container->set('pageCache', new FileCache);
|
||
```
|
||
|
||
> Подсказка: Если имя зависимости такое же, как и определение соответствующей зависимости, то её повторная регистрация в контейнере внедрения зависимостей не нужна.
|
||
|
||
Зависимость, зарегистрированная через `set()` создаёт экземпляр каждый раз, когда зависимость необходима.
|
||
Вы можете использовать [[yii\di\Container::setSingleton()]] для регистрации зависимости, которая создаст только один экземпляр:
|
||
|
||
```php
|
||
$container->setSingleton('yii\db\Connection', [
|
||
'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
|
||
'username' => 'root',
|
||
'password' => '',
|
||
'charset' => 'utf8',
|
||
]);
|
||
```
|
||
|
||
|
||
Разрешение зависимостей <span id="resolving-dependencies"></span>
|
||
----------------------
|
||
После регистрации зависимостей, вы можете использовать контейнер внедрения зависимостей для создания новых объектов,
|
||
и контейнер автоматически разрешит зависимости их экземпляра и их внедрений во вновь создаваемых объектах. Разрешение зависимостей рекурсивно, то есть
|
||
если зависимость имеет другие зависимости, эти зависимости также будут автоматически разрешены.
|
||
|
||
Вы можете использовать [[yii\di\Container::get()]] для создания новых объектов. Метод принимает имя зависимости, которым может быть имя класса, имя интерфейса или псевдоним.
|
||
Имя зависимости может быть или не может быть зарегистрировано через `set()` или `setSingleton()`.
|
||
Вы можете опционально предоставить список параметров конструктора класса и [конфигурацию](concept-configurations.md) для настройки созданного объекта.
|
||
Например,
|
||
|
||
```php
|
||
// "db" ранее зарегистрированный псевдоним
|
||
$db = $container->get('db');
|
||
|
||
// эквивалентно: $engine = new \app\components\SearchEngine($apiKey, ['type' => 1]);
|
||
$engine = $container->get('app\components\SearchEngine', [$apiKey], ['type' => 1]);
|
||
```
|
||
|
||
За кулисами, контейнер внедрения зависимостей делает гораздо больше работы, чем просто создание нового объекта.
|
||
Прежде всего, контейнер, осмотрит конструктор класса, чтобы узнать имя зависимого класса или интерфейса, а затем автоматически разрешит эти зависимости рекурсивно.
|
||
|
||
Следующий код демонстрирует более сложный пример. Класс `UserLister` зависит от объекта, реализующего интерфейс `UserFinderInterface`; класс `UserFinder` реализует этот интерфейс и зависит от
|
||
объекта `Connection`. Все эти зависимости были объявлены через тип подсказки параметров конструктора класса.
|
||
При регистрации зависимости через свойство, контейнер внедрения зависимостей позволяет автоматически разрешить эти зависимости и создаёт новый экземпляр `UserLister` простым вызовом `get('userLister')`.
|
||
|
||
```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');
|
||
|
||
// что эквивалентно:
|
||
|
||
$db = new \yii\db\Connection(['dsn' => '...']);
|
||
$finder = new UserFinder($db);
|
||
$lister = new UserLister($finder);
|
||
```
|
||
|
||
|
||
Практическое использование <span id="practical-usage"></span>
|
||
---------------
|
||
|
||
Yii создаёт контейнер внедрения зависимостей когда вы подключаете файл `Yii.php` во [входном скрипте](structure-entry-scripts.md)
|
||
вашего приложения. Контейнер внедрения зависимостей доступен через [[Yii::$container]]. При вызове [[Yii::createObject()]],
|
||
метод на самом деле вызовет метод контейнера [[yii\di\Container::get()|get()]], чтобы создать новый объект.
|
||
Как упомянуто выше, контейнер внедрения зависимостей автоматически разрешит зависимости (если таковые имеются) и внедрит их в только что созданный объект.
|
||
Поскольку Yii использует [[Yii::createObject()]] в большей части кода своего ядра для создания новых объектов, это означает,
|
||
что вы можете настроить глобальные объекты, имея дело с [[Yii::$container]].
|
||
|
||
Например, вы можете настроить по умолчанию глобальное количество кнопок в пейджере [[yii\widgets\LinkPager]]:
|
||
|
||
```php
|
||
\Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]);
|
||
```
|
||
|
||
Теперь, если вы вызовете в представлении виджет, используя следующий код, то свойство `maxButtonCount` будет инициализировано, как 5, вместо значения по умолчанию 10, как это определено в классе.
|
||
|
||
```php
|
||
echo \yii\widgets\LinkPager::widget();
|
||
```
|
||
|
||
Хотя, вы всё ещё можете переопределить установленное значение через контейнер внедрения зависимостей:
|
||
|
||
```php
|
||
echo \yii\widgets\LinkPager::widget(['maxButtonCount' => 20]);
|
||
```
|
||
Другим примером является использование автоматического внедрения зависимости через конструктор контейнера внедрения зависимостей.
|
||
Предположим, ваш класс контроллера зависит от ряда других объектов, таких как сервис бронирования гостиницы. Вы
|
||
можете объявить зависимость через параметр конструктора и позволить контейнеру внедрения зависимостей, разрешить её за вас.
|
||
|
||
```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);
|
||
}
|
||
}
|
||
```
|
||
|
||
Если у вас есть доступ к этому контроллеру из браузера, вы увидите сообщение об ошибке, который жалуется на то, что `BookingInterface`
|
||
не может быть создан. Это потому что вы должны указать контейнеру внедрения зависимостей, как обращаться с этой зависимостью:
|
||
|
||
```php
|
||
\Yii::$container->set('app\components\BookingInterface', 'app\components\BookingService');
|
||
```
|
||
|
||
Теперь, если вы попытаетесь получить доступ к контроллеру снова, то экземпляр `app\components\BookingService` будет создан и введён в качестве 3-го параметра конструктора контроллера.
|
||
|
||
|
||
Когда следует регистрировать зависимости <span id="when-to-register-dependencies"></span>
|
||
-----------------------------
|
||
|
||
Поскольку зависимости необходимы тогда, когда создаются новые объекты, то их регистрация должна быть сделана
|
||
как можно раньше. Ниже приведены рекомендуемые практики:
|
||
|
||
* Если вы разработчик приложения, то вы можете зарегистрировать зависимости во [входном скрипте](structure-entry-scripts.md) вашего приложения или в скрипте, подключённого во входном скрипте.
|
||
* Если вы разработчик распространяемого [расширения](structure-extensions.md), то вы можете зарегистрировать зависимости в загрузочном классе расширения.
|
||
|
||
|
||
Итог <span id="summary"></span>
|
||
-------
|
||
Как dependency injection, так и [service locator](concept-service-locator.md) являются популярными паттернами проектирования, которые позволяют
|
||
создавать программное обеспечение в слабосвязанной и более тестируемой манере.
|
||
Мы настоятельно рекомендуем к прочтению
|
||
[статью Мартина Фаулера](http://martinfowler.com/articles/injection.html), для более глубокого понимания dependency injection и service locator.
|
||
|
||
Yii реализует свой [service locator](concept-service-locator.md) поверх контейнера внедрения зависимостей.
|
||
Когда service locator пытается создать новый экземпляр объекта, он перенаправляет вызов на контейнер внедрения зависимостей.
|
||
Последний будет разрешать зависимости автоматически, как описано выше.
|
||
|