Yii2. Контейнер внедрения зависимостей (dependency injection container)

Контейнер внедрения зависимостей – это объект, который предназначен для создания других объектов со всеми необходимыми зависимостями. Под “зависимостями” понимаются другие объекты, которые должны быть созданы перед созданием желаемого объекта.

Yii2 создаёт контейнер внедрения зависимостей когда вы подключаете файл Yii.php во входном скрипте вашего приложения, а доступ к нему осуществляется через “Yii::$container”.

Для примера возьмем класс “SupportManager” и создадим его через контейнер внедрения зависимостей. Код этого класса:

<?php
 
namespace app\services;
 
use yii\mail\MailerInterface;
use app\forms\SupportRequestCreateForm;
 
class SupportManager
{
    private $mailer;
    private $adminEmail;
 
    public function __construct(MailerInterface $mailer, string $adminEmail)
    {
        $this->mailer = $mailer;
        $this->adminEmail = $adminEmail;
    }
 
    public function createRequest(SupportRequestCreateForm $form)
    {
        // ...
        $this->mailer->compose()
            ->setTo($this->adminEmail)
            ->setFrom($form->email)
            ->setSubject($form->subject)
            ->setTextBody($form->body)
            ->send();
        // ...
    }
}

Код контроллера из которого будет запрашиваться сгенерированный объект класса “SupportManager”:

<?php
 
namespace app\controllers;
 
use Yii;
use yii\web\Controller;
use app\forms\SupportRequestCreateForm;
use app\services\SupportManager;
 
class SiteController extends Controller
{
    // ...
 
    public function actionSupport()
    {
        $form = new SupportRequestCreateForm();
 
        if ($form->load(Yii::$app->request->post()) && $form->validate()) {
            Yii::$container->get(SupportManager::class)->createRequest($form);
            Yii::$app->session->setFlash('SupportRequestCreated');
 
            return $this->refresh();
        }
        return $this->render('contact', ['form' => $form]);
    }
 
    // ...
}

Создадим Bootstrap-файл с объявлением зависимостей для класса “SupportManager”. В “средних” проектах обычно создают разные Bootstrap-файлы для объявления Singleton’ов и остальных зависимостей (которые используют эти Singleton’ы), а в “крупных” проектах с большим количество модулей могут быть и десятки подобных файлов, но в нашем случае у нас будет только один:

<?php
 
namespace app\bootstrap;
 
use Yii;
use yii\di\Instance;
use yii\mail\MailerInterface;
use yii\base\BootstrapInterface;
use app\services\SupportManager;
 
class ServicesDefinitions implements BootstrapInterface
{
    public function bootstrap($app)
    {
        // Способ 1.
        // Обычно применяется когда классы-зависимости используются только один раз.
        // $app->container->setSingleton(SupportManager::class, function() use ($app) {
        //     return new SupportManager($app->mailer, $app->params['adminEmail']);
        // });
 
        // Способ 2.
        // Обычно применяется когда классы-зависимости используются неоднократно и в разных местах.
        // В данном случае Instance::of(MailerInterface::class) дает возможность не плодить экземпляры
        // Mailer'a, а использовать один и тот же объект.
        $app->container->setSingleton(MailerInterface::class, function() use($app) {
            return $app->mailer;
        });
        $app->container->setSingleton(SupportManager::class, [], [
            Instance::of(MailerInterface::class),
            $app->params['adminEmail']
        ]);
        // $app->container->setSingleton(ExampleOtherClass::class, [], [
        //     Instance::of(MailerInterface::class),
        //     $app->params['adminEmail']
        // ]);
    }
}

Подключаем созданный Bootstrap-файл в конфигурации Yii2:

<?php
 
$config = [
    // ...
    'bootstrap' => ['log', 'app\bootstrap\ServicesDefinitions'],
    // ...
];

После добавления “ServicesDefinitions” в раздел предварительной загрузки контейнер внедрения зависимостей Yii2 начнет работать. Приведу еще несколько примеров из документации:

$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);

Если создаваемый объект очень сложный, то для его построения стоит создать отдельный класс:

class FooBuilder
{
    public static function build($container, $params, $config)
    {
        $foo = new Foo(new Bar);
        // ... дополнительная инициализация ...
        return $foo;
    }
}
 
$container->set('Foo', ['app\helper\FooBuilder', 'build']);
 
$foo = $container->get('Foo');

Пример настройки компонентов Yii2 с помощью DIC:

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

Еще один важны момент, два создания Singleton’ов используется метод “$container->setSingleton()”, а для обычных объектов “$container->set()”.

Полезные ссылки:

Добавить комментарий