Введение в 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-