Внедрение зависимостей
Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy — урок 57 из 91.
В предыдущем уроке вы узнали, как композиция позволяет Car содержать объект Engine. Но создание зависимостей внутри класса сопряжено с проблемой — это делает код жестким и труднотестируемым. Внедрение зависимостей решает эту проблему, передавая зависимости извне, а не создавая их внутри.
Сравните эти два подхода:
<?php
// Без DI — зависимость создается внутри
class Car {
private Engine $engine;
public function __construct() {
$this->engine = new Engine(); // Жесткая связь
}
}
// С DI — зависимость передается извне
class Car {
public function __construct(private Engine $engine) {}
}
$engine = new Engine();
$car = new Car($engine); // Внедряется извне
Второй подход — это внедрение зависимостей. Класс Car не создает свой собственный двигатель — он получает его извне. Это простое изменение дает мощные преимущества: вы можете передавать различные типы двигателей, заменять реализации для тестирования, а сам класс становится более гибким.
Для еще большей гибкости внедряйте интерфейсы вместо конкретных классов:
<?php
interface EngineInterface {
public function start(): string;
}
class Car {
public function __construct(private EngineInterface $engine) {}
public function start(): string {
return $this->engine->start();
}
}
Теперь Car работает с любым классом, реализующим EngineInterface — бензиновым двигателем, электродвигателем или мок-объектом для тестирования. Такая развязка — причина, по которой внедрение зависимостей является основополагающим для написания поддерживаемых и тестируемых PHP-приложений.
Задание
ЛегкоДавайте создадим систему уведомлений, которая демонстрирует мощь внедрения зависимостей (dependency injection). Вместо жесткого кодирования способа отправки сообщений, вы создадите гибкую систему, в которой метод доставки можно легко заменить.
Вы организуете свой код в четырех файлах:
MessageSenderInterface.php— Определите интерфейс с именемMessageSenderInterfaceс единственным методомsend(string $recipient, string $message): string. Этот интерфейс устанавливает контракт, которому должен следовать любой отправитель сообщений.EmailSender.php— Создайте классEmailSender, который реализуетMessageSenderInterface. Подключите файл интерфейса. Методsend()должен возвращать"Email to $recipient: $message".NotificationService.php— Создайте классNotificationService, который получает свою зависимость через конструктор. Подключите файл интерфейса. Класс должен:- Принимать
MessageSenderInterfaceв своем конструкторе, используя продвижение свойств в конструкторе (constructor promotion) - Иметь метод
notify(string $recipient, string $message), который делегирует задачу внедренному отправителю и возвращает его результат
NotificationServiceзависит от интерфейса, а не от конкретного класса. Это означает, что вы можете внедрить любого отправителя, реализующего этот интерфейс, не меняя сам сервис.- Принимать
main.php— Подключите файлы EmailSender и NotificationService. Вы получите два входных значения: получателя и сообщение. Создайте экземплярEmailSender, внедрите его вNotificationService, затем вызовитеnotify()и выведите результат.
Этот паттерн делает ваш NotificationService гибким — он работает с любым отправителем, которого вы внедрите, будь то электронная почта, SMS или что-то, что вы еще не создали. Сервис не создает свои собственные зависимости; он получает их извне.
Шпаргалка
Внедрение зависимостей (Dependency Injection, DI) — это метод, при котором зависимости передаются в класс извне, а не создаются внутри него.
Без DI (жесткая связь):
<?php
class Car {
private Engine $engine;
public function __construct() {
$this->engine = new Engine(); // Создается внутри
}
}С DI (слабая связь):
<?php
class Car {
public function __construct(private Engine $engine) {}
}
$engine = new Engine();
$car = new Car($engine); // Внедряется извнеДля максимальной гибкости внедряйте интерфейсы вместо конкретных классов:
<?php
interface EngineInterface {
public function start(): string;
}
class Car {
public function __construct(private EngineInterface $engine) {}
public function start(): string {
return $this->engine->start();
}
}Это позволяет Car работать с любым классом, реализующим EngineInterface, что делает код более удобным в сопровождении и тестировании.
Попробуйте сами
<?php
require_once 'EmailSender.php';
require_once 'NotificationService.php';
// Чтение входных данных
$recipient = trim(fgets(STDIN));
$message = trim(fgets(STDIN));
// TODO: Создать экземпляр EmailSender
// TODO: Внедрить его в NotificationService
// TODO: Вызвать notify() и вывести результат
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово $thisМетодыСвойстваКонструктор (__construct)Деструктор (__destruct)Итоги — Простой калькулятор4Наследование
Основы наследованияКлючевое слово parent::Переопределение методовКлючевое слово finalАбстрактные классыИтоги — Иерархия сотрудников7Инкапсуляция
Public, Protected, PrivateПодробный разбор модификаторов доступаГеттеры и сеттерыСокрытие данныхConstructor Promotion (8.0)Итоги — Система учета студентов10Продвинутые концепции ООП
Композиция против наследованияВнедрение зависимостейАнонимные классыПеречисления (PHP 8.1)Файберы (PHP 8.1)Глубокое погружение в клонирование объектовГенераторы и итераторы13Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий2Пространства имен и автозагрузка
Введение в пространства именКлючевое слово useСтандарт автозагрузки PSR-4Автозагрузчик ComposerИтоги: структурированный проект5Интерфейсы и контракты
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовИнтерфейс против абстрактного классаType Hinting с интерфейсамиИтоги — Калькулятор фигур8Магические методы
Введение в магические методы__toString и __debugInfo__get, __set, __isset, __unset__call и __callStatic__clone и клонирование объектов__serialize и __unserializeИтоги — Кастомная коллекция11Система типов и обработка ошибок
Объявление типовNullable-типыТипы объединения и пересеченияКлассы исключенийИерархия пользовательских исключенийTry, Catch, FinallyИтоги — Валидатор форм14Проект: Управление библиотекой
Обзор проектаКлассы Book и User3Свойства классов
Свойства экземпляра и статические свойстваКонстанты в классахСтатические методы и свойстваПриватные и защищенные свойстваСвойства Readonly (PHP 8.1)Итоги — Менеджер банковского счета6Полиморфизм
Снова о переопределении методовПолиморфизм через интерфейсыType Hinting и Union TypesПозднее статическое связываниеИтоги — Payment Processor9Трейты
Введение в трейтыИспользование нескольких трейтовРазрешение конфликтов трейтовАбстрактные методы в трейтахТрейты и наследование12Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy