Dependency Injection в PHP: паттерн и контейнеры
Что такое Dependency Injection
Dependency Injection (DI) представляет собой паттерн, при котором объект получает зависимости извне, а не создаёт их самостоятельно. Это конкретная реализация принципа Inversion of Control.
Основная цель — снижение уровня coupling между модулями. Когда классы не зависят напрямую от конкретных реализаций, система становится гибкой и тестопригодной.
Inversion of Control и уровень связности
Inversion of Control переносит контроль над созданием объектов и управлением зависимостями на внешний компонент — контейнер. Вместо того чтобы класс сам решал, какой именно объект ему нужен, он лишь объявляет интерфейс.
Такой подход полностью устраняет tightly coupled код. Замена Google Maps на OpenStreetMap в StoreService, который зависит от GeolocationService, превращается в изменение одной строки конфигурации.
Роль Dependency Injection Container
Container — центральный элемент системы внедрения зависимостей. Он отвечает за создание объектов, разрешение constructor parameters и управление жизненным циклом сервисов.
Современные контейнеры соответствуют стандарту PSR-11. Согласно ему, для получения зависимости используется метод get($name), а для проверки наличия — has($name).
PSR-11 и совместимость контейнеров
Стандарт PSR-11 определяет общий интерфейс для контейнеров зависимостей в PHP. Это позволяет библиотекам работать с любым совместимым контейнером без привязки к конкретной реализации.
PHP-DI полностью соответствует PSR-11. Благодаря этому библиотека может использоваться как самостоятельный контейнер или как дополнение к контейнерам Symfony и Laravel.
Автоматическое связывание (Autowiring)
Autowiring — одна из самых мощных возможностей PHP-DI. По информации разработчиков, autowiring по type-hints в конструкторе покрывает 80% случаев использования при нулевой конфигурации.
Контейнер самостоятельно анализирует конструктор класса и рекурсивно разрешает все необходимые зависимости. Разработчику остаётся только указать, какую реализацию интерфейса использовать.
Конфигурация PHP-DI
PHP-DI использует мощный и читаемый синтаксис конфигурации. Для привязки интерфейса к реализации применяется конструкция:
return [
NotifierInterface::class => get(EmailNotifier::class),
];
Можно явно определять правила создания объекта через create()->constructor(get(Bar::class)). Это особенно полезно при работе со сторонними библиотеками.
Работа с окружением и конфигурационными файлами
Контейнер поддерживает чтение значений из environment variables напрямую в конфигурации через функцию env('DB_HOST', 'default value'). Это позволяет хранить чувствительные данные вне кода.
Для расширения существующих конфигурационных массивов предусмотрен хелпер add([...]). Он особенно удобен при настройке Monolog, когда нужно добавлять новые обработчики в уже существующий список.
Внедрение зависимостей в callable
Одна из удобных особенностей PHP-DI — возможность внедрять зависимости напрямую в функции и методы. Метод call() автоматически разрешает параметры:
$container->call(function (Logger $logger, EntityManager $em) {
// ...
});
Это удобно при работе с контроллерами, middleware и обработчиках очередей.
Использование контейнера как фабрики
Иногда требуется создать объект с дополнительными параметрами. Метод make(Foo::class, $parameters) позволяет передать недостающие constructor parameters, при этом остальные зависимости контейнер разрешит автоматически.
Такой подход сочетает преимущества ручного создания объектов и автоматического разрешения зависимостей.
Когда стоит внедрять Dependency Injection
DI особенно полезен в проектах, где важна гибкость архитектуры — при разработке модульных приложений, библиотек и сервисов, которым может потребоваться замена реализаций на разных окружениях.
Однако в небольших скриптах или прототипах избыточное использование контейнера может усложнить понимание потока выполнения. Всё зависит от контекста и масштаба проекта.

