DI-контейнер в Yii 2: как настроить правильно с первого раза
Разбираемся, как устроен контейнер зависимостей в Yii 2, чем set отличается от setSingleton и почему регистрация в компоненте приложения работает не так, как вы ожидаете.

Типичная сцена: разработчик пишет Yii::$container->set('logger', Logger::class), потом в контроллере делает new UserService() и удивляется, что зависимость не подтянулась. Контейнер в Yii 2 не перехватывает new — он работает только там, где объект создаётся через Yii::createObject() или через автоматическое разрешение конструктора. Ниже — как правильно регистрировать зависимости, где грань между set и setSingleton и почему регистрация в components приложения живёт по своим правилам.
Материал для Yii 2.0.x (актуальная ветка 2.0.49+), PHP 7.4–8.3. В Yii 3 контейнер вынесен в отдельный пакет yiisoft/di и совместим с PSR-11 — там API другой, здесь его не разбираем.
Что делает контейнер и чего не делает
DI-контейнер Yii 2 (yii\di\Container) — это фабрика, которая умеет по имени класса построить объект, подставив в конструктор нужные зависимости. Разрешение идёт двумя способами: по явным правилам, которые вы задали, и через рефлексию — контейнер читает типы аргументов конструктора и пытается собрать их сам.
Ключевое, что путает новичков: контейнер срабатывает не всегда. Он включается в трёх случаях.
- Вы вызвали
Yii::createObject($config)напрямую. - Объект создаётся фреймворком — контроллеры, компоненты, виджеты, поведения проходят через
createObject. - Одна зависимость тянет другую: если контейнер строит класс A, а в конструкторе A есть тип B, он построит и B.
Оператор new SomeClass() контейнер не видит вообще. Это не баг, это архитектурное решение: Yii не подменяет стандартный оператор языка.
Точка входа: Yii::$container
Глобальный контейнер приложения доступен как Yii::$container. Его же использует Yii::createObject(). Регистрировать правила удобнее всего в config/bootstrap.php или в отдельном файле, который подключается до обработки запроса — тогда правила действуют для всего приложения.
set против setSingleton: главная развилка
Оба метода регистрируют правило разрешения зависимости. Разница в жизненном цикле объекта.
set()— при каждом обращении контейнер создаёт новый экземпляр.setSingleton()— экземпляр создаётся один раз при первом обращении и переиспользуется дальше.
Выбор не косметический. Синглтон, хранящий состояние между запросами, безопасен в классическом FPM-режиме (процесс живёт один запрос), но становится источником утечек и гонок в долгоживущих воркерах вроде RoadRunner или Swoole, где процесс обрабатывает тысячи запросов. Логгер, коннект к БД, конфиг — кандидаты в синглтоны. Сервис, накапливающий данные конкретного запроса, синглтоном быть не должен.
Если сомневаетесь между set и setSingleton — начните с set. Лишний экземпляр объекта дешевле, чем состояние, протекающее между запросами, которое вы будете ловить неделю.
Способы задать правило
Контейнер принимает несколько форматов определения. Все рабочие, но применяются в разных ситуациях.
use yii\di\Container;
$container = Yii::$container;
// 1. Класс на интерфейс — самый частый случай
$container->set(
app\services\NotifierInterface::class,
app\services\EmailNotifier::class
);
// 2. С параметрами конструктора
$container->set(app\services\SmsNotifier::class, [
'class' => app\services\SmsNotifier::class,
// публичные свойства объекта после создания
'senderName' => 'MyShop',
]);
// 3. Замыкание — когда сборка нетривиальна
$container->set('app\services\PaymentGateway', function ($container, $params, $config) {
$client = $container->get(app\services\HttpClient::class);
return new app\services\PaymentGateway($client, 'production');
});
// 4. Готовый объект как синглтон
$container->setSingleton('app\services\Clock', new app\services\SystemClock());Формат с массивом: значения с ключом-строкой без ведущего типа трактуются как публичные свойства объекта, которые контейнер выставит после вызова конструктора. Позиционные аргументы конструктора передаются через второй параметр get() или через ключ __construct() в правиле.
Как связать интерфейс с реализацией
Это то, ради чего контейнер и нужен. Ваш сервис зависит от интерфейса, контейнер подставляет конкретный класс.
// Интерфейс
interface NotifierInterface
{
public function send(string $to, string $message): void;
}
// Сервис зависит от абстракции, не от класса
class OrderService
{
private NotifierInterface $notifier;
public function __construct(NotifierInterface $notifier)
{
$this->notifier = $notifier;
}
}
// Регистрация в bootstrap.php
Yii::$container->set(
NotifierInterface::class,
EmailNotifier::class
);
// Теперь OrderService собирается сам
$service = Yii::createObject(OrderService::class);
// в конструктор автоматически попадёт EmailNotifierОбратите внимание: OrderService нигде не зарегистрирован явно. Контейнер построил его через рефлексию, увидел тип NotifierInterface, нашёл для него правило и подставил EmailNotifier. Заменить реализацию на SMS — одна строка в конфиге, код сервиса не трогаете.
Регистрация в components — отдельная история
Здесь чаще всего ломаются ожидания. Компоненты приложения (Yii::$app->db, Yii::$app->mailer) регистрируются не в контейнере, а в секции components конфига приложения. Это другой механизм — yii\di\ServiceLocator, от которого наследуется приложение.
Разница практическая:
| Свойство | Контейнер (Yii::$container) | Компоненты (Yii::$app->component) |
|---|---|---|
| Механизм | Container | ServiceLocator |
| Доступ | createObject / инъекция в конструктор | Yii::$app->id |
| Жизненный цикл | set — новый, setSingleton — один | всегда один на приложение (ленивый синглтон) |
| Где настраивать | bootstrap.php, container-конфиг | секция components в config |
Практический вывод: инфраструктурные сервисы, к которым обращаетесь как Yii::$app->что-то, — это компоненты. Зависимости, которые вы хотите инъектировать в конструкторы своих классов, — это контейнер. Не пытайтесь тянуть компонент через рефлексию конструктора: локатор и контейнер о существовании друг друга по умолчанию не знают.
Как подружить одно с другим
Если сервис в контейнере должен получить компонент приложения, отдайте его явно через замыкание:
Yii::$container->set(app\services\ReportBuilder::class, function () {
return new app\services\ReportBuilder(
Yii::$app->db, // компонент из ServiceLocator
Yii::$app->cache
);
});Где это ломается
Пять ситуаций, в которые упираются на практике.
- Циклическая зависимость. A требует B, B требует A. Контейнер уйдёт в рекурсию и упрётся в лимит памяти PHP с
Allowed memory size exhausted. Лечится разрывом цикла: сделайте одну из зависимостей ленивой или инъектируйте через сеттер, а не конструктор. - Скалярный аргумент в конструкторе. Если у зависимости в конструкторе есть
string $apiKeyбез значения по умолчанию, рефлексия не знает, что подставить, и броситyii\di\NotInstantiableExceptionилиMissing required parameter. Задайте значение явно в правиле через__construct(). - Union-типы и nullable. Аргумент вида
Logger|nullконтейнер разрешит вnull, если правила дляLoggerнет. Молча. Объект соберётся, а зависимости не будет — ошибка вылезет позже в рантайме. - setSingleton в долгоживущем процессе. Под RoadRunner синглтон переживает запрос вместе с накопленным состоянием. Данные одного пользователя утекают в ответ другому. Проверяйте, что синглтоны stateless.
- Правило зарегистрировано после createObject. Если вы вызвали
createObjectраньше, чем добавили правило в bootstrap, подставится реализация по умолчанию или прилетит исключение. Регистрируйте правила до обработки запроса.
Порядок, который не подведёт
Чтобы не переделывать через месяц:
- Зависимости своих сервисов принимайте через конструктор и типизируйте интерфейсами, а не классами.
- Все правила
set/setSingletonдержите в одном месте —config/bootstrap.phpили отдельном файле контейнера, подключённом до запроса. - Синглтоном делайте только stateless-объекты; всё, что хранит данные запроса, — через
set. - Компоненты приложения (
Yii::$app->*) не смешивайте с контейнером; мост между ними — явное замыкание.
Что дальше
Проверьте свой проект на два запаха: прямые new для классов с зависимостями (их контейнер не собирает) и синглтоны с полями, которые меняются в ходе запроса. Если планируете переезд на RoadRunner или Swoole — пройдитесь по всем setSingleton заранее. Дальше стоит прочитать раздел про DI-контейнер в официальном гайде Yii 2 и разобрать, как createObject используется внутри самого фреймворка при создании контроллеров — это лучший источник рабочих паттернов.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: Dependency Injection Container — Yii 2.0 Definitive Guide, Class yii\di\Container — Yii 2 API Documentation
Частые вопросы
Почему зависимость не подставилась, хотя правило зарегистрировано?
Чем set отличается от setSingleton?
А если у меня другая версия — Yii 3?
Почему Yii::$app->db не подтягивается в конструктор моего сервиса?
Грозит ли утечка данных, если оставить всё на setSingleton?
Что делать при ошибке о циклической зависимости или переполнении памяти?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.