Dependency Injection в PHP: полное руководство с примерами кода

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

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

Начать курс

Dependency Injection в PHP: на практике

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

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

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

class UserController {
    private $db;
    public function __construct() {
        $this->db = new MySQLConnection('localhost', 'user', 'pass');
    }
}

Такой подход создает жесткую связь (tight coupling). Если вы захотите переключиться на PostgreSQL или использовать мок-объект для тестов, придется менять код контроллера. Dependency Injection решает эту проблему: зависимости передаются извне, а не создаются внутри класса.

Основные преимущества DI:

  • Снижение связанности кода
  • Упрощение тестирования (легко подменять зависимости)
  • Повторное использование классов
  • Чистая архитектура и соблюдение принципа инверсии зависимостей (DIP)

Способы внедрения зависимостей в PHP

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

1. Внедрение через конструктор

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

<?php
interface DatabaseInterface {
    public function query(string $sql): array;
}

class MySQLDatabase implements DatabaseInterface {
    public function query(string $sql): array {
        // Реальная логика работы с MySQL
        return ['id' => 1, 'name' => 'John'];
    }
}

class UserRepository {
    private DatabaseInterface $db;
    
    public function __construct(DatabaseInterface $db) {
        $this->db = $db;
    }
    
    public function getUser(int $id): array {
        return $this->db->query("SELECT * FROM users WHERE id = $id");
    }
}

// Использование:
$db = new MySQLDatabase();
$repo = new UserRepository($db);
$user = $repo->getUser(1);
?>

Обратите внимание: мы используем интерфейс DatabaseInterface. Это позволяет легко подменить реализацию — например, на тестовую заглушку.

2. Внедрение через сеттеры

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

<?php
class MailService {
    private ?MailerInterface $mailer = null;
    
    public function setMailer(MailerInterface $mailer): void {
        $this->mailer = $mailer;
    }
    
    public function send(string $to, string $subject, string $body): void {
        if ($this->mailer === null) {
            throw new \RuntimeException('Mailer not configured');
        }
        $this->mailer->send($to, $subject, $body);
    }
}

// Использование:
$mailService = new MailService();
$mailService->setMailer(new SmtpMailer());
$mailService->send('user@example.com', 'Hello', 'Body');
?>

Минус подхода — вы не гарантируете, что зависимость будет установлена до вызова метода send(). Поэтому для обязательных зависимостей предпочтительнее конструктор.

3. Внедрение через метод

Зависимость передается непосредственно в метод, где она нужна. Полезно для редких или контекстных зависимостей.

<?php
class ReportGenerator {
    public function generate(DataFetcherInterface $fetcher, string $reportType): array {
        $data = $fetcher->fetch($reportType);
        // Обработка данных...
        return ['report' => $data];
    }
}

// Использование:
$generator = new ReportGenerator();
$report = $generator->generate(new APIDataFetcher(), 'monthly');
?>

Контейнер зависимостей (Dependency Injection Container)

Когда проект разрастается, ручное создание объектов с зависимостями становится утомительным. На помощь приходит DI-контейнер — объект, который управляет созданием и хранением зависимостей.

Рассмотрим простую реализацию контейнера:

<?php
class SimpleContainer {
    private array $bindings = [];
    private array $instances = [];
    
    public function set(string $id, callable $resolver): void {
        $this->bindings[$id] = $resolver;
    }
    
    public function get(string $id): mixed {
        if (!isset($this->instances[$id])) {
            $this->instances[$id] = $this->bindings[$id]($this);
        }
        return $this->instances[$id];
    }
}

// Настройка контейнера
$container = new SimpleContainer();
$container->set(DatabaseInterface::class, function($c) {
    return new MySQLDatabase();
});
$container->set(UserRepository::class, function($c) {
    return new UserRepository($c->get(DatabaseInterface::class));
});

// Использование — получаем готовый объект со всеми зависимостями
$repo = $container->get(UserRepository::class);
$user = $repo->getUser(1);
?>

В реальных проектах чаще используют готовые библиотеки, такие как PHP-DI, Symfony DependencyInjection или Laravel Service Container. Они умеют автоматически определять зависимости через рефлексию (autowiring).

Практический пример: приложение с DI

Соберем все вместе — создадим мини-приложение, которое отправляет уведомления пользователям через разные каналы.

<?php
// 1. Интерфейсы
interface NotificationChannel {
    public function send(string $to, string $message): void;
}

interface UserRepositoryInterface {
    public function findByEmail(string $email): ?array;
}

// 2. Реализации
class EmailChannel implements NotificationChannel {
    public function send(string $to, string $message): void {
        echo "Email to {$to}: {$message}\n";
    }
}

class SMSChannel implements NotificationChannel {
    public function send(string $to, string $message): void {
        echo "SMS to {$to}: {$message}\n";
    }
}

class UserRepository implements UserRepositoryInterface {
    private array $users = [
        'john@example.com' => ['name' => 'John', 'phone' => '+1234567890'],
        'jane@example.com' =>                

Блоги

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