Внедрение зависимостей (Dependency Injection) в PHP для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое Dependency Injection в PHP и зачем это нужно?

Представьте, что вы строите дом. Вместо того чтобы самому делать каждый кирпич, вы просто заказываете их и используете готовые. Dependency Injection (DI) в программировании работает по тому же принципу — вы не создаёте объекты внутри класса, а получаете их извне. Это делает ваш код гибким, тестируемым и понятным.

В PHP, как и в любом объектно-ориентированном языке, классы часто зависят от других классов. Проблема возникает, когда зависимости создаются прямо внутри класса — это называется жёсткой связью (tight coupling). Dependency Injection решает эту проблему, передавая зависимости извне.

Проблема без Dependency Injection

Рассмотрим типичный пример, когда класс UserService сам создаёт объект базы данных:

<?php
class Database {
    public function query($sql) {
        // Логика запроса
        return "Результат: " . $sql;
    }
}

class UserService {
    private $db;
    
    public function __construct() {
        // Жёсткая связь — класс сам создаёт зависимость
        $this->db = new Database();
    }
    
    public function getUser($id) {
        return $this->db->query("SELECT * FROM users WHERE id = $id");
    }
}

$service = new UserService();
echo $service->getUser(1);
?>

Проблемы такого подхода:

  • Нельзя заменить Database на другую реализацию (например, для тестов)
  • Сложно тестировать — нельзя подставить заглушку (mock)
  • Код становится монолитным и трудным для расширения

Решение: Внедрение зависимости через конструктор

Теперь перепишем тот же класс, используя Dependency Injection:

<?php
class UserService {
    private $db;
    
    // Зависимость передаётся извне
    public function __construct(Database $db) {
        $this->db = $db;
    }
    
    public function getUser($id) {
        return $this->db->query("SELECT * FROM users WHERE id = $id");
    }
}

// Создаём зависимость отдельно
$database = new Database();
$service = new UserService($database);
echo $service->getUser(1);
?>

Теперь UserService не знает, как создаётся Database. Он просто использует то, что ему передали. Это и есть внедрение зависимости.

Виды Dependency Injection в PHP

Существует три основных способа внедрения зависимостей. Рассмотрим каждый с примерами.

1. Внедрение через конструктор (Constructor Injection)

Самый распространённый и рекомендуемый способ. Зависимости передаются при создании объекта через конструктор.

<?php
class Logger {
    public function log($message) {
        echo "[LOG]: " . $message;
    }
}

class OrderProcessor {
    private $logger;
    
    // Зависимость в конструкторе
    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }
    
    public function process($order) {
        // Обработка заказа
        $this->logger->log("Заказ обработан: " . $order);
    }
}

$logger = new Logger();
$processor = new OrderProcessor($logger);
$processor->process("Заказ #123");
?>

Плюсы: зависимость всегда доступна, объект всегда в валидном состоянии. Минусы: если зависимостей много, конструктор может стать громоздким.

2. Внедрение через сеттер (Setter Injection)

Используется, когда зависимость может быть необязательной или её нужно заменить в процессе работы объекта.

<?php
class MailService {
    private $transport;
    
    // Сеттер для зависимости
    public function setTransport($transport) {
        $this->transport = $transport;
    }
    
    public function send($to, $message) {
        if ($this->transport) {
            echo "Отправка " . $message . " на " . $to . " через " . $this->transport;
        } else {
            echo "Транспорт не настроен!";
        }
    }
}

$mail = new MailService();
$mail->setTransport("SMTP");
$mail->send("user@example.com", "Привет!");
?>

Плюсы: гибкость, можно менять зависимость в рантайме. Минусы: можно забыть вызвать сеттер, и объект будет неполным.

3. Внедрение через интерфейс (Interface Injection)

Используется реже, но полезен для сложных систем. Класс реализует интерфейс, который обязывает принять зависимость.

<?php
interface LoggerAwareInterface {
    public function setLogger(Logger $logger);
}

class UserManager implements LoggerAwareInterface {
    private $logger;
    
    public function setLogger(Logger $logger) {
        $this->logger = $logger;
    }
    
    public function createUser($name) {
        // Создание пользователя
        if ($this->logger) {
            $this->logger->log("Создан пользователь: " . $name);
        }
    }
}

$logger = new Logger();
$manager = new UserManager();
$manager->setLogger($logger);
$manager->createUser("Иван");
?>

Практический пример: Рефакторинг с Dependency Injection

Давайте посмотрим, как DI улучшает реальный код. Представим, что у нас есть класс для отправки уведомлений.

Плохой код (без DI)

<?php
class NotificationService {
    private $emailSender;
    private $smsSender;
    
    public function __construct() {
        // Жёсткая связь — класс сам создаёт все зависимости
        $this->emailSender = new EmailSender();
        $this->smsSender = new SmsSender();
    }
    
    public function notify($user, $message) {
        $this->emailSender->send($user->email, $message);
        $this->smsSender->send($user->phone, $message);
    }
}
?>

Хороший код (с DI)

<?php
interface SenderInterface {
    public function send($recipient, $message);
}

class EmailSender implements SenderInterface {
    public function send($recipient, $message) {
        echo "Email отправлен на " . $recipient . ": " . $message;
    }
}

class SmsSender implements SenderInterface {
    public function send($recipient, $message) {
        echo "SMS отправлен на " . $recipient . ": "                

Блоги

Рекомендаций по книгам