В современном веб‑разработке важна архитектура, позволяющая быстро менять поведение приложения. Yii2 предоставляет мощный контейнер зависимостей, который позволяет внедрять сервисы в контроллеры без глобальных синглтонов. В этой статье мы разберём, как зарегистрировать сервисы в контейнере и использовать их в контроллерах, используя примеры Yii2 dependency injection. Такой подход упрощает тестирование и повышает гибкость кода.
Разбор задачи
Когда приложение растёт, контроллеры начинают зависеть от множества сервисов: почтового, кеша, платежного и т.д. Если каждый контроллер сам создаёт эти сервисы или использует глобальный объект, код становится тяжеловесным и трудно тестируемым. Нужно вынести создание сервисов в один слой и позволить контейнеру управлять их жизненным циклом. В Yii2 это достигается через сервис‑локатор и контейнер зависимостей, где сервисы регистрируются как фабрики или singleton‑объекты.
Пошаговое решение
Первый шаг – определить интерфейс, который будет использовать контроллер. Далее создаём реализацию и регистрируем её в компоненте components приложения. При этом можно указать фабрику, чтобы сервис создавался по запросу. Затем в контроллере объявляем зависимость через конструктор или свойство, и Yii2 автоматически подставит нужный экземпляр, когда контроллер будет создан. Такой механизм позволяет полностью избавиться от глобальных синглтонов, улучшить тестируемость и упростить поддержку кода.
Рабочий код с комментариями
// app/config/di.php
return [
'components' => [
'mailer' => [
'class' => yiiswiftmailerMailer::class,
'transport' => [
'class' => yiiswiftmailertransportSendmailTransport::class,
],
],
],
];
// Controller using constructor injection
namespace appcontrollers;
use yiiwebController;
use yiiswiftmailerMailer;
class SiteController extends Controller
{
private $mailer;
public function __construct($id, $module, Mailer $mailer, $config = [])
{
parent::__construct($id, $module, $config);
$this->mailer = $mailer; // dependency injected
}
public function actionSend()
{
$this->mailer->compose()
->setTo('user@example.com')
->setSubject('Test')
->send();
}
}
Типичные ошибки
1. Подавление ошибок при неверном типе. Если конструктор ожидает Mailer, а передаётся строка, PHP бросит TypeError. Убедитесь, что в конфиге указан правильный класс.
2. Избыточный Service Locator. Часто разработчики берут компонент из Yii::$app->mailer вместо DI, что усложняет тестирование. Предпочитайте конструктор.
3. Игнорирование конфигурационных параметров. Если компонент требует настройки (например, DSN), забыть задать это в config, и сервис будет неинициализирован.
FAQ
Как протестировать контроллер с DI?
Создайте мок Mailer и передайте его в конструктор. В тесте опустите конфиг, используйте $controller = new SiteController(‘site’, $module, $mockMailer); После этого проверяйте, что метод send был вызван.
Можно ли менять тип сервиса в рантайме?
Да, но только через контейнер. Используйте Yii::$container->set(Mailer::class, $customMailer); Это заменит глобальный биндинг для всех последующих вызовов.
Что делать, если нужно несколько реализаций одного интерфейса?
Определите разные алиасы: Yii::$container->set(‘mailService’, EmailMailer::class); и в контроллере тип интерфейса, а не конкретный класс. Тесты могут переключать реализацию, задавая другой класс под тот же алиас.
Заключение и чек‑лист
Ниже список действий, которые гарантируют правильную работу DI в Yii2:
- Определите все сервисы в config/di.php.
- Используйте конструктор‑инъекцию в контроллерах.
- Не прибегайте к Service Locator, если можно.
- Проверьте типы в конструкторе, чтобы избежать Runtime Error.
- Тестируйте контроллеры с моками.
- При необходимости меняйте реализацию через Yii::$container->set().
