Гид по технологиям

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

5 min read Программирование Обновлено 05 Jan 2026
Внедрение зависимостей в PHP: простое руководство
Внедрение зависимостей в PHP: простое руководство

Иллюстрация: что такое внедрение зависимостей в 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 в проекте

  1. Идентифицировать сервисы и зависимости (логгеры, БД, конфигурация, шаблонизаторы).
  2. Создать контейнер с базовыми регистрациями (singletons, factories).
  3. Перенести зависимости в конструкторы постепенно, начиная с модулей, которые покрываются тестами.
  4. Добавить тесты с моками/фейками для ключевых сервисов.
  5. Рефакторить: где логично, заменить service-locator на явную инъекцию.

Чек-листы по ролям

Разработчик:

  • Перечислил зависимости в конструкторе или через атрибут.
  • Проверил, что класс не создаёт свои зависимости напрямую без причины.
  • Добавил модульные тесты с заглушками.

Технический руководитель:

  • Решил стратегию (контейнер + auto-wiring или definitions).
  • Утвердил правила оформления зависимостей.
  • Контролирует покрытие тестами критичных модулей.

DevOps/Release инженер:

  • Обеспечил, чтобы конфигурационные значения (credentials, endpoints) были доступны контейнеру через окружение/секреты.
  • Проследил, что контейнеры/сервисы не захардкожены.

Пример простого playbook для перехода к DI

  1. Выделить модуль с 1–2 testable классами.
  2. Ввести контейнер в bootstrap проекта.
  3. Перенести зависимости в конструктор, зарегистрировать их в контейнере.
  4. Запустить тесты, исправить сломавшиеся места.
  5. Повторять для следующих модулей.

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 для понимания используемых библиотек.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство