Menu
Coddy logo textTech

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

Часть раздела Объектно-ориентированное программирование путешествия по 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.

challenge icon

Задание

Легко

Давайте создадим систему уведомлений, демонстрирующую возможности внедрения dependency. Вместо жёсткого задания способа отправки сообщений вы создадите гибкую систему, в которой способ доставки можно легко заменить.

Вы организуете код в четырёх файлах:

  • MessageSenderInterface.php: Define interface с именем MessageSenderInterface с одним method send(string $recipient, string $message): string. Этот interface устанавливает контракт, которому должен соответствовать любой отправитель сообщений.
  • EmailSender.php: Create class EmailSender, который implements MessageSenderInterface. Подключите файл interface. method send() должен возвращать "Email to [recipient]: [message]".
  • NotificationService.php: Create class NotificationService, который получает свою dependency через constructor. Подключите файл interface. class должен:
    • Accept MessageSenderInterface в своём constructor, используя constructor promotion
    • Have method notify(string $recipient, string $message), который delegates вызов внедрённому отправителю и возвращает его результат
    Обратите внимание, что NotificationService зависит от interface, а не от конкретного class. Это означает, что вы можете Inject любого отправителя, который implements interface, не изменяя сервис.
  • main.php: Подключите файлы EmailSender и NotificationService. Вы получите два входных значения: recipient и message. Create EmailSender, 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() и вывести результат

?>
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор PHP