Внедрение зависимостей
Часть раздела Объектно-ориентированное программирование путешествия по 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); // Внедряется извне
Второй подход — dependency injection. Car не создаёт собственный engine — он получает его. Это простое изменение даёт значительные преимущества: ты можешь передавать разные типы engine, заменять реализации для тестирования, и class становится более гибким.
Для ещё большей гибкости внедряйте интерфейсы вместо конкретных классов:
<?php
interface EngineInterface {
public function start(): string;
}
class Car {
public function __construct(private EngineInterface $engine) {}
public function start(): string {
return $this->engine->start();
}
}
Теперь Car работает с любым class, реализующим EngineInterface, — с бензиновым двигателем, электромотором или имитацией для тестирования. Такое разделение зависимостей — вот почему dependency injection является основой создания сопровождаемых и тестируемых приложений на PHP.
Задание
ЛегкоДавайте создадим систему уведомлений, демонстрирующую возможности внедрения dependency. Вместо жёсткого задания способа отправки сообщений вы создадите гибкую систему, в которой способ доставки можно легко заменить.
Вы организуете код в четырёх файлах:
MessageSenderInterface.php: Define interface с именемMessageSenderInterfaceс одним methodsend(string $recipient, string $message): string. Этот interface устанавливает контракт, которому должен соответствовать любой отправитель сообщений.EmailSender.php: Create classEmailSender, который implementsMessageSenderInterface. Подключите файл interface. methodsend()должен возвращать"Email to [recipient]: [message]".NotificationService.php: Create classNotificationService, который получает свою dependency через constructor. Подключите файл interface. class должен:- Accept
MessageSenderInterfaceв своём constructor, используя constructor promotion - Have method
notify(string $recipient, string $message), который delegates вызов внедрённому отправителю и возвращает его результат
NotificationServiceзависит от interface, а не от конкретного class. Это означает, что вы можете Inject любого отправителя, который implements interface, не изменяя сервис.- Accept
main.php: Подключите файлы EmailSender и NotificationService. Вы получите два входных значения: recipient и message. CreateEmailSender, Inject его вNotificationService, затем вызовитеnotify()и выведите результат.
Этот шаблон сохраняет гибкость вашего NotificationService. Он работает с любым отправителем, которого вы Inject, будь то электронная почта, SMS или что-то, что вы ещё не создали. Сервис не создаёт собственные dependency; он получает их снаружи.
Попробуйте сами
<?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Модификаторы доступа подробноГеттеры и сеттерыСокрытие информацииПродвижение конструктора (8.0)Повторение — система учёта студентов10Продвинутые концепции ООП
Композиция и наследованиеВнедрение зависимостейАнонимные классыПеречисления (PHP 8.1)Файберы (PHP 8.1)Глубокое погружение в клонирование объектовГенераторы и итераторы2Пространства имён и автозагрузка
Введение в пространства имёнКлючевое слово useСтандарт автозагрузки PSR-4Автозагрузчик ComposerПовторение — структурированный проект5Интерфейсы и контракты
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовИнтерфейс и абстрактный классАннотация типов с интерфейсамиПовторение — калькулятор фигур8Магические методы
Введение в магические методы__toString и __debugInfo__get, __set, __isset, __unset__call и __callStatic__clone и клонирование объектов__serialize и __unserializeПовторение — пользовательская коллекция3Свойства классов
Свойства экземпляра и статические свойстваКонстанты в классахСтатические методы и свойстваПриватные и защищённые свойстваСвойства только для чтения (PHP 8.1)Повторение — менеджер банковского счёта6Полиморфизм
Повторение переопределения методовПолиморфизм через интерфейсыАннотации типов и объединённые типыПозднее статическое связываниеПовторение — обработчик платежей9Трейты
Введение в трейтыИспользование нескольких трейтовРазрешение конфликтов трейтовАбстрактные методы в трейтахТрейты и наследование12Паттерны проектирования, часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн StrategyПотренируйтесь самостоятельно: Онлайн-компилятор PHP