Внедрение зависимостей в PHP: простое руководство

Коротко о сути
Внедрение зависимостей — это шаблон проектирования: вместо того чтобы класс сам создавал свои зависимости, они передаются ему извне. Проще представить это как рабочего с набором инструментов: когда начинается новая задача (класс/метод), рабочий автоматически берёт нужные инструменты.
Определение в одну строку: dependency injection — это передача внешних зависимостей в класс/функцию извне, чтобы отделить создание объектов от их использования.
Важно: внедрение зависимостей — не волшебство, а архитектурное решение. Оно помогает главным образом в больших кодовых базах, где нужны тестируемость и гибкость.
Почему это полезно
- Ослабляет связность: классы не зависят от конкретных реализаций.
- Улучшает тестирование: зависимости можно подменять моками/фейками.
- Централизует конфигурацию: настройки и сервисы собираются в одном месте (контейнере).
- Поддерживает принципы SOLID, особенно раздельную ответственность и инверсию зависимостей.
Примечание: внедрение зависимостей улучшает дизайн, но не заменяет хорошую архитектуру и здравый смысл.
Установка Apex Container и Twig
В примерах используется Apex Container — лёгкая реализация контейнера зависимостей, и Twig как пример сервиса. Предполагается, что PHP уже установлен. Проверьте Composer так:
composer --versionЕсли команда не найдена, можно установить Composer командой (для Unix-подобных систем):
sudo curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composerСоздайте пустую директорию проекта и выполните:
composer require apex/container
composer require twig/twigОбе библиотеки будут загружены в подкаталог /vendor/.
Пример: класс Car и автоматическая инъекция
Сначала создадим класс Car (файл car.php):
Затем создаём файл, который использует контейнер (например index.php):
set('model', 'Jaguar');
$cntr->set('color', 'silver');
// Create our car object
$car = $cntr->make('Car');
$car2 = $cntr->make('car', ['color' => 'red']);
Вывод при запуске:
I'm a silver Jaguar and have a Twig\Loader\ArrayLoader
I'm a red Jaguar and have a Twig\Loader\ArrayLoaderПояснение: контейнер посмотрел конструктор Car, увидел требуемые аргументы (model, color, ArrayLoader), подставил значения, которые вы установили ($cntr->set(…)) и автоматически создал экземпляр ArrayLoader — так работает auto-wiring.
Важно: use декларация вверху файла указывает, какую именно реализацию ожидает конструктор; контейнер использует эту информацию для создания нужного сервиса.
Инъекция через атрибуты в свойства
Кроме конструктора, контейнер может инъектировать зависимости прямо в свойства класса через атрибуты. Это полезно, когда хочется держать список зависимостей ближе к месту использования.
Изменённый car.php с атрибутом Inject:
cntr::class . "\n";
}
}
После добавления вызова $car->getCost(); вывод будет:
I'm a silver Jaguar and have a Twig\Loader\ArrayLoader
I'm a red Jaguar and have a Twig\Loader\ArrayLoader
Class is Apex\Container\ContainerПояснение: контейнер отсканировал свойства класса, обнаружил атрибут Inject и подставил экземпляр Container.
Получение значений из контейнера вручную
Иногда легче или логичнее получить значение из контейнера самодельно, а не полагаться на инъекцию. Для этого используется метод get().
Внутри getCost() можно сделать так:
function getCost()
{
$price = $this->cntr->get('car_price');
echo "The price is $price\n";
}
А в тестовом коде установить цену:
$cntr->set('car_price', 24995);Вывод может быть таким (проверьте значения в коде):
I'm a silver Jaguar and have a Twig\Loader\ArrayLoader
I'm a red Jaguar and have a Twig\Loader\ArrayLoader
The price is 24995Важно: не путайте ручное извлечение с инъекцией зависимости — оба подхода имеют право на жизнь. Инъекция лучше, когда зависимость постоянна и требуется для тестирования; get() удобен для динамических значений.
Когда внедрение зависимостей не подходит
- Простые скрипты и однострочные утилиты: введение контейнера добавляет лишнюю сложность.
- Очень мелкие проекты без тестирования: выигрыш от DI может не окупить время на его настройку.
- Когда зависимость явно и единственно создаётся внутри, и она не меняется, то явное создание (new) иногда проще.
Контрпример: если класс единственная точка доступа к ресурсу и он не тестируется, DI может только усложнить код.
Альтернативные подходы
- Сервис-локатор (service locator): централизованный объект, из которого классы просят сервисы. Более простой путь, но он скрывает зависимости и ухудшает тестируемость по сравнению с явной инъекцией.
- Фабрики: фабричный метод или фабрика создаёт объекты по конфигурации и возвращает их. Хорошо сочетается с DI, когда создание сложное.
- Контейнеры с конфигом (definitions): вместо автоматического wiring вы описываете, как собирать объекты в файле конфигурации.
Выбор зависит от размера проекта, команды и требований к тестированию.
Психологические модели и эвристики
- Правило явных зависимостей: если класс использует что-то — перечислите это в конструкторе.
- Минимальная поверхность API: держите число зависимостей в конструкторе маленьким (обычно ≤4).
- Инверсия управления: отдавайте создание внешнему компоненту (контейнеру/фабрике).
Эти простые эвристики помогают не разрастать класс и упрощают поддержку.
Мини-методология внедрения DI в проекте
- Идентифицировать сервисы и зависимости (логгеры, БД, конфигурация, шаблонизаторы).
- Создать контейнер с базовыми регистрациями (singletons, factories).
- Перенести зависимости в конструкторы постепенно, начиная с модулей, которые покрываются тестами.
- Добавить тесты с моками/фейками для ключевых сервисов.
- Рефакторить: где логично, заменить service-locator на явную инъекцию.
Чек-листы по ролям
Разработчик:
- Перечислил зависимости в конструкторе или через атрибут.
- Проверил, что класс не создаёт свои зависимости напрямую без причины.
- Добавил модульные тесты с заглушками.
Технический руководитель:
- Решил стратегию (контейнер + auto-wiring или definitions).
- Утвердил правила оформления зависимостей.
- Контролирует покрытие тестами критичных модулей.
DevOps/Release инженер:
- Обеспечил, чтобы конфигурационные значения (credentials, endpoints) были доступны контейнеру через окружение/секреты.
- Проследил, что контейнеры/сервисы не захардкожены.
Пример простого playbook для перехода к DI
- Выделить модуль с 1–2 testable классами.
- Ввести контейнер в bootstrap проекта.
- Перенести зависимости в конструктор, зарегистрировать их в контейнере.
- Запустить тесты, исправить сломавшиеся места.
- Повторять для следующих модулей.
Decision flow для выбора способа инъекции
flowchart TD
A[Нужна ли тестируемость и гибкость?] -->|Да| B[Использовать DI]
A -->|Нет| C[Оставить явное создание]
B --> D{Зависимости простые или составные?}
D -->|Простые| E[Инъекция через конструктор]
D -->|Составные| F[Использовать фабрики/definitions]
E --> G[Добавить автосвязывание]
F --> G
C --> H[Прямое new]Краткий глоссарий
- Контейнер: центральное хранилище сервисов/настроек, умеет создавать и отдавать объекты.
- Auto-wiring: автоматическое определение и создание зависимостей на основе типов/аннотаций.
- Service locator: шаблон, где объекты запрашивают сервисы из центрального места.
- Атрибуты: метаданные в коде (PHP 8+), используемые контейнером для инъекции.
Критерии приёмки
- Классы не создают внешние сервисы напрямую без обоснования.
- Ключевые сервисы зарегистрированы в контейнере и доступны для замены в тестах.
- Покрыты тестами модули, где была сделана рефакторизация в DI.
Резюме
Внедрение зависимостей — простой и мощный приём для улучшения архитектуры PHP-приложений. На примере Apex Container показаны основные приёмы: make()/set()/get(), автоматическое связывание по типу и инъекция через атрибуты. DI повышает тестируемость и гибкость, но требует дисциплины и здравого подхода: не стоит применять его там, где он усложняет решение.
Важно: начинайте с малого, рефакторьте модули постепенно и добавляйте тесты — тогда преимущества DI станут заметны быстро.
Дополнительные источники: руководства по Apex Container и документация Twig для понимания используемых библиотек.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone