Внедрение зависимостей в PHP с Apex Container

Кратко: внедрение зависимостей (Dependency Injection, DI) — это способ обеспечить класс всеми «инструментами» (зависимостями), которые он требует, без ручного создания этих инструментов внутри класса. В статье показано, как установить Apex Container, сделать авто‑внедрение (autowiring), использовать атрибутную инъекцию и выбирать зависимости вручную через get()/set(). Подключены практические советы, модели принятия решений и чек‑листы для команд.
Что такое внедрение зависимостей?
В одной строке: внедрение зависимостей — это передача объекту внешних ресурсов и настроек вместо их создания внутри объекта.
Аналогия: представьте рабочего с чемоданом инструментов, который идёт вместе с программой и доставляет нужные инструменты к месту работы. Инструменты — это переменные, объекты, замыкания и другие ресурсы.
Почему это полезно:
- Уменьшает связность между классами.
- Улучшает тестируемость (можно легко подменять зависимости моками).
- Позволяет централизовать конфигурацию — менять реализации в одном месте.
Ключевые термины:
- Контейнер — реестр или фабрика, который хранит и создаёт зависимости (инструменты).
- Авто‑внедрение (autowiring) — контейнер автоматически создаёт и связывает требуемые типы.
- Атрибутная инъекция — внедрение в свойства класса через атрибуты (PHP 8+).
Важно: DI не решает плохой дизайн сам по себе. Он делает управление зависимостями явным и контролируемым.
Установка Apex Container
Мы будем использовать Apex Container — простая и понятная реализация контейнера. Предполагается, что PHP установлен. Проверьте Composer:
composer --versionЕсли Composer не установлен, его можно поставить, выполнив (в Linux/macOS):
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 или run.php):
set('model', 'Jaguar');
$cntr->set('color', 'silver');
$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 из Twig — это автосвязывание.
Расширение: атрибутная инъекция
Можно не только внедрять через конструктор, но и в свойства класса через атрибуты. Измените car.php так:
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 и поместил в свойство экземпляр самого контейнера — это полезно, если объекту нужен доступ к фабрике/реестру.
Получение инструментов вручную (get/set)
Иногда удобнее не инжектить всё заранее, а запросить нужные данные из контейнера в момент выполнения.
Пример изменения getCost():
function getCost()
{
$price = $this->cntr->get('car_price');
echo "The price is $price\n";
}В тесте перед вызовом getCost() установите цену:
$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Примечание: код хранит только число; валюту и формат вывода вы определяете сами (в примере — условно в долларах).
Модели мышления и эвристики при использовании DI
- Разделяйте ответственность: контейнер управляет созданием/конфигурацией, объекты — бизнес‑логикой.
- Предпочитайте инъекцию через конструктор для обязательных зависимостей и через свойства/атрибуты для опциональных.
- Если объект требует много аргументов, подумайте о фасаде или фабричном методе.
Хорошая эвристика: «Если хочется new внутри класса — это повод подумать о DI». Это делает зависимости видимыми и подменяемыми.
Когда DI не подходит (контрпримеры)
- Простые скрипты/утилиты: DI может добавить лишний уровень абстракции в одноразовых скриптах.
- Очень мелкие классы с одной‑двумя зависимостями — иногда проще вручную передать зависимости.
- Когда архитектура строго модульна и внедрение приводит к циклическим зависимостям — лучше пересмотреть дизайн.
Важно: DI помогает, но не заменяет рефакторинг плохих архитектур.
Альтернативные подходы
- Service Locator: объект, из которого классы сами запрашивают зависимости. Проще, но скрывает зависимости и ухудшает тестируемость.
- Ручная фабрика: централизованный набор фабрик, создающих сложные объекты по мере необходимости.
- Контейнеры с конфигурационными файлами (YAML, PHP arrays) — дают явную карту зависимостей.
Выбор зависит от размера проекта, команды и требований к тестируемости.
Мини‑методология внедрения DI в проект (шаги)
- Выделить сервисы и зависимости в небольшом модуле.
- Добавить контейнер (например, Apex) и зарегистрировать базовые сервисы.
- Поменять создание объектов на контейнер.make()/container.get().
- Добавить тесты с моками через injection точек.
- Постепенно рефакторить остальные модули.
Дерево решений (Mermaid)
flowchart TD
A[Нужно ли DI?] -->|Проект > 1 разработчика| B{Сложность зависимостей}
B -->|Много| C[Использовать DI контейнер]
B -->|Мало| D[Ручная передача через конструктор]
A -->|Нет, одноразовый скрипт| E[Не использовать DI]
C --> F{Требуются автосвязывание и атрибуты?}
F -->|Да| G[Выбрать контейнер с autowiring]
F -->|Нет| H[Рассмотреть фабрики/Service Locator]Чек‑лист по ролям
Разработчик:
- Заменить new на container.make() там, где появляются зависимости.
- Делать зависимости явными в конструкторе.
- Писать модульные тесты с подменой зависимостей.
Архитектор:
- Определить корневой набор сервисов и политики жизненного цикла.
- Выбрать стратегию конфигурации контейнера (код или файлы).
QA/DevOps:
- Убедиться, что контейнер корректно работает в средах (dev/staging/prod).
- Проверить время старта приложения при создании большого числа сервисов.
Критерии приёмки
- Сервисы создаются контейнером, а не через new внутри бизнес‑логики.
- Модули покрыты тестами, где зависимости подменены моками.
- Нет скрытых зависимостей (все обязательные зависимости видны в конструкторе).
Краткий словарь (1‑строчная справка)
- Контейнер — центр управления зависимостями; создаёт и хранит сервисы.
- Autowiring — автоматическое сопоставление типов и создание экземпляров.
- Атрибутная инъекция — пометка свойства атрибутом для автоматического внедрения.
Edge‑case gallery (загрраничные случаи и подсказки)
- Циклическая зависимость: распознать и разорвать через фабрику или ленивую загрузку.
- Конфигурация, зависящая от окружения: регистрируйте разные реализации в зависимости от env.
- Долгая инициализация сервисов: использовать ленивую загрузку (lazy) или отложенную инициализацию.
Безопасность и приватность
Контейнер сам по себе не хранит секретов в открытом виде, но будьте осторожны: если в контейнере регистрируются креденшелы или ключи, управлять доступом к конфигурации нужно через безопасные хранилища и механизмы шифрования.
Краткое резюме
- DI делает зависимости явными и управляемыми — это улучшает тестируемость и поддерживаемость.
- Apex Container показывает базовую работу DI: set(), get(), make(), автосвязывание и атрибутная инъекция.
- Не применять DI там, где он усложняет простые сценарии; выбирать инструменты под размер проекта.
Важно: начните с малого — извлеките несколько сервисов в контейнер, добавьте тесты, и постепенно расширяйте использование DI.
Похожие материалы
Лучшие онлайн‑конвертеры файлов — выбор и советы
Индексные карточки в Word
Автоматизация поиска и замены в Word
Интеграция Excel и Word через VBA
32‑бит против 64‑бит Windows: что выбрать