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

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

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

Начать курс

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";

//                    

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