PHP-DI для PHP: полное руководство с примерами

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

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

Начать курс

Введение в PHP-DI: что это и зачем нужно

PHP-DI (PHP Dependency Injection) — это мощная библиотека для внедрения зависимостей (Dependency Injection, DI) в PHP-приложениях. Она реализует паттерн Inversion of Control (IoC) и предоставляет контейнер для автоматического разрешения зависимостей между классами.

Зачем нужна PHP-DI? В современных PHP-проектах (особенно на фреймворках вроде Laravel, Symfony, Slim) классы часто зависят от других классов. Без DI-контейнера вам приходится вручную создавать все зависимости — это приводит к:

  • Жесткой связанности кода (tight coupling)
  • Сложности тестирования
  • Дублированию кода при создании объектов
  • Трудностям с поддержкой и расширением

PHP-DI решает эти проблемы, автоматически определяя, какие классы нужны вашему коду, и создавая их с правильными зависимостями. Библиотека поддерживает:

  • Автоматическое внедрение через конструктор
  • Определение зависимостей через аннотации (phpDoc)
  • Конфигурацию через массивы, YAML или PHP-файлы
  • Ленивую загрузку (lazy loading)
  • Интеграцию с популярными фреймворками

Установка PHP-DI

PHP-DI устанавливается через Composer — стандартный менеджер пакетов для PHP:

composer require php-di/php-di

Если вы используете фреймворк Slim, установите интеграционный пакет:

composer require php-di/slim-bridge

Для Symfony есть отдельный пакет:

composer require php-di/symfony-bridge

После установки подключите автозагрузчик Composer в вашем проекте:

require __DIR__ . '/vendor/autoload.php';

Быстрый старт с примером кода

Рассмотрим простейший пример использования PHP-DI. Допустим, у нас есть класс Mailer, который зависит от класса Logger:

<?php
// Класс Logger
class Logger {
    public function log($message) {
        echo "[LOG]: " . $message . "\n";
    }
}

// Класс Mailer, который зависит от Logger
class Mailer {
    private $logger;
    
    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }
    
    public function send($to, $subject) {
        $this->logger->log("Отправка письма на {$to}: {$subject}");
        // Логика отправки...
    }
}

// Создаем DI-контейнер
$container = new DI\Container();

// Получаем объект Mailer — все зависимости разрешаются автоматически
$mailer = $container->get('Mailer');
$mailer->send('user@example.com', 'Привет!');
?>

В этом примере PHP-DI автоматически создает экземпляр Logger и передает его в конструктор Mailer. Вам не нужно писать new Logger() — контейнер делает это за вас.

Основные методы и функции PHP-DI

1. Container::get() — получение объекта из контейнера

Что делает: Извлекает объект по его имени (классу, интерфейсу или псевдониму) из контейнера. Если объект еще не создан, контейнер создает его и все его зависимости.

Параметры:

  • string $name — имя класса, интерфейса или псевдоним

Пример:

$container = new DI\Container();
$logger = $container->get('Logger'); // Создает и возвращает объект Logger
$mailer = $container->get('Mailer'); // Создает Mailer с Logger внутри

2. Container::set() — регистрация определения

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

Параметры:

  • string $name — имя класса или псевдоним
  • mixed $definition — определение (строка, массив, замыкание, объект Definition)

Пример:

$container = new DI\Container();
$container->set('Logger', function() {
    return new Logger('production.log');
});
$container->set('MailerInterface', 'SmtpMailer'); // Привязка интерфейса к реализации

3. Container::has() — проверка существования определения

Что делает: Проверяет, зарегистрировано ли определение для указанного имени в контейнере.

Параметры:

  • string $name — имя класса или псевдоним

Пример:

$container = new DI\Container();
$container->set('Logger', \DI\create('Logger'));
if ($container->has('Logger')) {
    echo "Logger зарегистрирован";
}

4. Container::make() — создание объекта с параметрами

Что делает: Создает объект указанного класса, позволяя передать дополнительные параметры, которые переопределяют автоматическое разрешение зависимостей.

Параметры:

  • string $name — имя класса
  • array $parameters — ассоциативный массив параметров для конструктора

Пример:

$container = new DI\Container();
// Переопределяем параметр $logger для конкретного вызова
$mailer = $container->make('Mailer', [
    'logger' => new FileLogger('custom.log')
]);

5. \DI\create() — создание определения для класса

Что делает: Создает объект определения, который указывает контейнеру, как создавать экземпляр класса. Позволяет задать параметры конструктора и вызовы методов.

Параметры:

  • string $class — имя класса
  • array $parameters — параметры конструктора (опционально)

Пример:

use function DI\create;

$container = new DI\Container();
$container->set('Logger', create('Logger')
    ->constructor('app.log')
    ->method('setLevel', 'DEBUG')
);

6. \DI\get() — ссылка на другой сервис

Что делает: Создает ссылку на другой сервис в контейнере. Используется для указания зависимостей в определениях.

Параметры:

  • string $entry — имя сервиса, на который ссылаемся

Пример:

use function DI\create;
use function DI\get;

$container = new DI\Container();
$container->set('Logger', create('Logger'));
$container->set('Mailer', create('Mailer')
    ->constructor(get('Logger')) // Внедряем Logger через ссылку
);

7. \DI\autowire() — автоматическое связывание

Что делает: Включает автоматическое разрешение зависимостей для указанного класса. Контейнер сам определяет, какие параметры нужны конструктору, и создает их.

Параметры:

  • string $class — имя класса

Пример:

use function DI\autowire;

$container = new DI\Container();
$container->set('Mailer', autowire('Mailer'));
// Контейнер автоматически разрешит все зависимости Mailer

8. \DI\env() — получение переменной окружения

Что делает: Позволяет получить значение переменной окружения с возможностью указать значение по умолчанию. Полезно для конфигурации.

Параметры:

  • string $name — имя переменной окружения
  • mixed $default — значение по умолчанию (опционально)

Пример:

use function DI\env;

$container = new DI\Container();
$container->set('db.host', env('DB_HOST', 'localhost'));
$container->set('db.port', env('DB_PORT', 3306));

9. ContainerBuilder::addDefinitions() — добавление определений из файла

Что делает: Позволяет загрузить определения из внешнего файла (PHP, YAML, JSON). Упрощает организацию конфигурации.

Параметры:

  • string|array $definitions — путь к файлу или массив определений

Пример:

use DI\ContainerBuilder;

$builder = new ContainerBuilder();
$builder->addDefinitions('config/services.php');
$builder->addDefinitions('config/logger.php');
$container = $builder->build();

// Файл config/services.php:
// return [
//     'Mailer' => DI\autowire('Mailer'),
//     'Logger' => DI\create('Logger')->constructor('app.log'),
// ];

10. Container::call() — вызов функции/метода с внедрением зависимостей

Что делает: Вызывает указанную функцию или метод, автоматически внедряя зависимости из контейнера. Полезно для контроллеров и обработчиков.

Параметры:

  • callable $callable — вызываемая функция или метод
  • array $parameters — дополнительные параметры (опционально)

Пример:

$container = new DI\Container();
$container->set('Logger', \DI\create('Logger'));

$result = $container->call(function(Logger $logger, $message) {
    $logger->log($message);
    return "Логгирование выполнено";
}, ['message' => 'Тестовое сообщение']);

Реальный практический пример использования

Рассмотрим реальный пример — создание простого REST API с использованием Slim Framework и PHP-DI. Мы создадим контроллер для управления пользователями.

<?php
// 1. Определяем классы

interface UserRepositoryInterface {
    public function findById($id);
    public function save(array $data);
}

class MySQLUserRepository implements UserRepositoryInterface {
    private $pdo;
    
    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
    
    public function findById($id) {
        $stmt = $this->pdo->prepare("SELECT * FROM users WHERE id = ?");
        $stmt->execute([$id]);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }
    
    public function save(array $data) {
        $stmt = $this->pdo->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
        return $stmt->execute([$data['name'], $data['email']]);
    }
}

class UserController {
    private $repository;
    private $logger;
    
    public function __construct(UserRepositoryInterface $repository, Logger $logger) {
        $this->repository = $repository;
        $this->logger = $logger;
    }
    
    public function getUser($request, $response, $args) {
        $id = $args['id'];
        $user = $this->repository->findById($id);
        
        if (!$user) {
            $this->logger->log("Пользователь {$id} не найден");
            return $response->withStatus(404)->withJson(['error' => 'User not found']);
        }
        
        return $response-                    

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