Pimple для PHP: полное руководство по простому DI-контейнеру
1. Что такое Pimple и зачем он нужен?
Pimple — это легковесный контейнер внедрения зависимостей (Dependency Injection Container) для PHP, разработанный Fabien Potencier, создателем Symfony Framework. В отличие от громоздких DI-контейнеров, Pimple представляет собой минималистичное решение, которое можно внедрить в любой проект без лишних накладных расходов. Библиотека реализует паттерн Service Locator и предоставляет простой API для управления объектами и их зависимостями.
Основная задача Pimple — упростить управление зависимостями в PHP-приложениях. Вместо того чтобы создавать объекты вручную с помощью new и передавать их в конструкторы, вы регистрируете сервисы в контейнере, а затем запрашиваете их по имени. Контейнер автоматически разрешает зависимости, создает объекты один раз (singleton) или каждый раз заново (factory), и управляет их жизненным циклом. Это делает код более модульным, тестируемым и поддерживаемым.
Pimple идеально подходит для небольших и средних проектов, где не требуется полный функционал Symfony DI или PHP-DI. Он не использует аннотации, не требует сложной конфигурации и работает начиная с PHP 5.3. Библиотека активно используется в Silex (микро-фреймворк) и других PHP-проектах, где важна простота и скорость.
2. Установка
Pimple устанавливается через Composer — стандартный менеджер пакетов для PHP. Выполните в терминале:
composer require pimple/pimple
После установки подключите автозагрузчик Composer в вашем PHP-файле:
require_once __DIR__ . '/vendor/autoload.php';
Если у вас еще нет composer.json, создайте его:
{
"require": {
"pimple/pimple": "^3.5"
}
}
3. Быстрый старт — минимальный рабочий пример
Создайте файл index.php и добавьте следующий код:
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Pimple\Container;
// Создаем контейнер
$container = new Container();
// Определяем простой сервис (замыкание)
$container['greeting'] = function ($c) {
return 'Hello, World!';
};
// Определяем сервис с параметром
$container['personal_greeting'] = function ($c) {
return function ($name) use ($c) {
return 'Hello, ' . $name . '!';
};
};
// Получаем сервисы
echo $container['greeting'] . "\n"; // Hello, World!
echo $container['personal_greeting']('Alice') . "\n"; // Hello, Alice!
// Определяем сервис-объект
class Logger {
public function log($message) {
echo "[LOG] $message\n";
}
}
$container['logger'] = function ($c) {
return new Logger();
};
$container['logger']->log('Application started');
Запустите скрипт: php index.php. Вы увидите вывод трех сообщений. Этот пример демонстрирует базовую регистрацию и получение сервисов из контейнера.
4. Основные методы и классы
4.1. Pimple\Container::__construct()
Сигнатура: public function __construct(array $values = [])
Описание: Создает новый экземпляр контейнера. Можно передать массив предопределенных значений/сервисов.
$container = new Container([
'db.host' => 'localhost',
'db.name' => 'test'
]);
4.2. offsetSet($id, $value) (магический метод через [])
Сигнатура: public function offsetSet($id, $value): void
Описание: Регистрирует сервис или значение в контейнере по указанному ключу. Если $value — замыкание, оно будет выполнено при первом обращении.
$container['config'] = ['debug' => true];
$container['mailer'] = function ($c) {
return new Mailer($c['config']);
};
4.3. offsetGet($id) (магический метод через [])
Сигнатура: public function offsetGet($id): mixed
Описание: Извлекает сервис или значение из контейнера. Если сервис определен как замыкание, оно выполняется, и результат кэшируется (для повторных вызовов возвращается тот же объект).
$mailer = $container['mailer'];
$sameMailer = $container['mailer']; // Тот же объект (singleton)
4.4. offsetExists($id)
Сигнатура: public function offsetExists($id): bool
Описание: Проверяет, зарегистрирован ли сервис или значение с указанным ключом.
if (isset($container['mailer'])) {
echo 'Mailer service exists';
}
4.5. offsetUnset($id)
Сигнатура: public function offsetUnset($id): void
Описание: Удаляет сервис или значение из контейнера. Если сервис был ранее создан, его экземпляр также удаляется.
unset($container['mailer']); // Удаляем сервис
4.6. factory($callable)
Сигнатура: public static function factory($callable): \Closure
Описание: Оборачивает замыкание в фабрику. При каждом обращении к сервису будет создаваться новый экземпляр объекта, а не возвращаться кэшированный.
use Pimple\Container;
$container['user_factory'] = $container->factory(function ($c) {
return new User();
});
$user1 = $container['user_factory']; // Новый объект
$user2 = $container['user_factory']; // Другой новый объект
4.7. protect($callable)
Сигнатура: public static function protect($callable): \Closure
Описание: Позволяет хранить замыкание как обычное значение, а не как сервис. Замыкание не будет выполнено при обращении, а вернется как есть.
$container['callback'] = $container->protect(function ($event) {
echo "Event: $event\n";
});
$fn = $container['callback']; // Получаем само замыкание
$fn('user.login'); // Вызываем его вручную
4.8. raw($id)
Сигнатура: public function raw($id): mixed
Описание: Возвращает исходное определение сервиса (замыкание или значение), не выполняя его. Полезно для отладки или модификации сервисов.
$container['config'] = ['debug' => true];
$raw = $container->raw('config'); // ['debug' => true]
$container['mailer'] = function ($c) {
return new Mailer($c['config']);
};
$rawClosure = $container->raw('mailer'); // Возвращает замыкание
4.9. extend($id, $callable)
Сигнатура: public function extend($id, $callable): void
Описание: Расширяет существующий сервис. Принимает замыкание, которое получает исходный сервис и контейнер, и возвращает модифицированную версию.
$container['logger'] = function ($c) {
return new Logger();
};
$container->extend('logger', function ($logger, $c) {
$logger->addHandler(new FileHandler('/var/log/app.log'));
return $logger;
});
$logger = $container['logger']; // Расширенный логгер
4.10. keys()
Сигнатура: public function keys(): array
Описание: Возвращает массив всех зарегистрированных ключей сервисов.
$container['db'] = function () { return new PDO('sqlite::memory:'); };
$container['cache'] = function () { return new ArrayCache(); };
$allKeys = $container->keys(); // ['db', 'cache']
5. Реальный практический пример (150+ строк кода)
Рассмотрим пример небольшого приложения для отправки email-уведомлений с использованием Pimple. Создадим структуру проекта:
<?php
// Пример: Email-уведомления с использованием Pimple
require_once __DIR__ . '/vendor/autoload.php';
use Pimple\Container;
// --- Классы приложения ---
interface MailerInterface {
public function send(string $to, string $subject, string $body): bool;
}
class SmtpMailer implements MailerInterface {
private string $host;
private int $port;
private string $username;
private string $password;
public function __construct(string $host, int $port, string $username, string $password) {
$this->host = $host;
$this->port = $port;
$this->username = $username;
$this->password = $password;
}
public function send(string $to, string $subject, string $body): bool {
// Имитация отправки
echo "Sending email to $to via SMTP ($this->host:$this->port)...\n";
echo "Subject: $subject\n";
echo "Body: $body\n";
return true;
}
}
class LogMailer implements MailerInterface {
private MailerInterface $mailer;
private LoggerInterface $logger;
public function __construct(MailerInterface $mailer, LoggerInterface $logger) {
$this->mailer = $mailer;
$this->logger = $logger;
}
public function send(string $to, string $subject, string $body): bool {
$this->logger->log("Attempting to send email to $to");
$result = $this->mailer->send($to, $subject, $body);
$this->logger->log($result ? "Email sent successfully" : "Email failed");
return $result;
}
}
interface LoggerInterface {
public function log(string $message): void;
}
class FileLogger implements LoggerInterface {
private string $logFile;
public function __construct(string $logFile) {
$this->logFile = $logFile;
}
public function log(string $message): void {
$timestamp = date('Y-m-d H:i:s');
file_put_contents($this->logFile, "[$timestamp] $message\n", FILE_APPEND);
echo "[LOG] $message\n";
}
}
class NotificationService {
private MailerInterface $mailer;
private array $config;
public function __construct(MailerInterface $mailer, array $config) {
$this->mailer = $mailer;
$this->config = $config;
}
public function sendWelcomeEmail(string $email, string $name): void {
$subject = "Welcome to " . $this->config['app_name'];
$body = "Hello $name,\n\nThank you for joining our service!";
$this->mailer->send($email, $subject, $body);
}
public function sendPasswordReset(string $email, string $token): void {
$subject = "Password Reset Request";
$body = "Your password reset token: $token\n\nIf you didn't request this, ignore this email.";
$this->mailer->send($email, $subject, $body);
}
}
// --- Настройка контейнера Pimple ---
$container = new Container();
// Конфигурация
$container['config'] = [
'app_name' => 'MyApp',
'smtp_host' => 'smtp.example.com',
'smtp_port' => 587,
'smtp_username' => 'user@example.com',
'smtp_password' => 'secret',
'log_file' => __DIR__ . '/app.log',
];
// Базовый SMTP-отправитель (singleton)
$container['smtp_mailer'] = function ($c) {
$config = $c['config'];
return new SmtpMailer(
$config['smtp_host'],
$config['smtp_port'],
$config['smtp_username'],
$config['smtp_password']
);
};
// Логгер (singleton)
$container['logger'] = function ($c) {
return new FileLogger($c['config']['log_file']);
};
// Декорированный mailer с логированием (singleton)
$container['mailer'] = function ($c) {
return new LogMailer($c['smtp_mailer'], $c['logger']);
};
// Сервис уведомлений (singleton)
$container['notification_service'] = function ($c) {
return new NotificationService($c['mailer'], $c['config']);
};
// Фабрика для создания временных токенов (каждый вызов - новый)
$container['token_factory'] = $container->factory(function ($c) {
return bin2hex(random_bytes(16));
});
// --- Использование ---
$notificationService = $container['notification_service'];
// Отправляем приветственное письмо
$notificationService->sendWelcomeEmail('user@example.com', 'John Doe');
// Отправляем сброс пароля
$notificationService->sendPasswordReset('admin@example.com', $container['token_factory']);
// Проверяем, что логгер работает
echo "\nLog file contents:\n";
echo file_get_contents($container['config']['log_file']) . "\n";
// Также в библиотеке
Авторизация и аутентификация в Kotlin: Ktor, JWT и Spring Security
Отправка email на Java: полное руководство по JavaMail и Jakarta Mail
Интернационализация (i18n) в Kotlin: полное руководство
CLI фреймворк для JavaScript (Node.js): Commander.js – создание консольных приложений