Удивительно, что на Хабре всё ещё нет статей об этом гениальном DI контейнере для PHP.
Почему гениальном? Потому, что весь код этого творения укладывается в 80 строк – маленький объект с большими возможностями.
Контейнер представляет из себя один класс, и его подключение в проект выглядит следующим образом:
require_once '/path/to/Pimple.php';
Создание контейнера так же просто:
$container = new Pimple();
Как и многие другие DI контейнеры, Pimple поддерживает два вида данных: сервисы и параметры.
Объявление параметров
Объявить параметры в Pimple очень просто: используем контейнер как простой массив:
// Объявляем параметр
$container['cookie_name'] = 'SESSION_ID';
$container['session_storage_class'] = 'SessionStorage';
Объявление сервисов
Сервис — некий объект, часть системы, которая выполняет свою конкретную задачу.
Например, сервисами могут являться: объект, предоставляющий соединение с базой данных, отвечающий за отправку почты, шаблонизацию выводимых данных и т.д.
В Pimple сервисы определяются как анонимные функции, возвращающие объект сервиса:
// Объявление сервисов
$container['session_storage'] = function ($c) {
return new $c['session_storage_class']($c['cookie_name']);
};
$container['session'] = function ($c) {
return new Session($c['session_storage']);
};
Обратите внимание, что анонимная функция имеет доступ к текущему контейнеру и это позволяет использовать в ней другие параметры или сервисы.
Объекты сервиса создаются только при обращении к ним, так что порядок объявления не имеет никакого значения.
Пользоваться созданными сервисами так же просто:
// Получение объекта сервиса
$session = $container['session'];
// Предыдущая строка равносильна следующему коду
// $storage = new SessionStorage('SESSION_ID');
// $session = new Session($storage);
Объявление сервисов «Синглтонов»
По умолчанию при каждом вызове Pimple возвращает новый объект сервиса. Если же требуется один экземпляр на всё приложение, всё, что вам необходимо сделать – обернуть объявление в метод share():
$container['session'] = $container->share(function ($c) {
return new Session($c['session_storage']);
});
Объявление функций
Так как Pimple рассматривает все анонимные функции как объявление сервисов, то для объявления именно функций в контейнере необходимо лишь обернуть всё это дело в метод protect():
$container['random'] = $container->protect(function () { return rand(); });
Изменение сервисов после их объявления
В некоторых случаях может понадобиться изменение поведения уже объявленного сервиса. Тогда можно использовать метод extend() для регистрации дополнительного кода, который будет выполнен сразу же после создания сервиса:
$container['mail'] = function ($c) {
return new Zend_Mail();
};
$container['mail'] = $container->extend('mail', function($mail, $c) {
$mail->setFrom($c['mail.default_from']);
return $mail;
});
Первым параметром в данную функцию передается имя сервиса, которое нужно дополнить, а вторым – функция, принимающая в качестве аргументов объект сервиса и текущий контейнер. В итоге при обращении к сервису получается объект, возвращаемый данной функцией.
Если же сервис был «Синглтоном», необходимо повторно обернуть код дополнения сервиса методом share(), иначе дополнения будут вызываться каждый раз при обращении к сервису:
$container['twig'] = $container->share(function ($c) {
return new Twig_Environment($c['twig.loader'], $c['twig.options']);
});
$container['twig'] = $container->share($container->extend('twig', function ($twig, $c) {
$twig->addExtension(new MyTwigExtension());
return $twig;
}));
Доступ к функции, возвращающей сервис
Каждый раз, когда вы обращаетесь к сервису, Pimple автоматически вызывает функцию его объявления. Если же требуется получить прямой доступ именно к функции объявления, можно использовать метод raw():
$container['session'] = $container->share(function ($c) {
return new Session($c['session_storage']);
});
$sessionFunction = $container->raw('session');
Повторное использование готового контейнера
Если вы от проекта к проекту используете одни и те же библиотеки, вы можете создать готовые контейнеры для повторного использования. Всё, что нужно сделать – это расширить класс Pimple:
class SomeContainer extends Pimple
{
public function __construct()
{
$this['parameter'] = 'foo';
$this['object'] = function () { return stdClass(); };
}
}
И вы можете с лёгкостью использовать данный готовый контейнер внутри другого контейнера:
$container = new Pimple();
// Объявление сервисов и параметров основного контейнера
// ...
// Вставка другого контейнера
$container['embedded'] = $container->share(function () { return new SomeContainer(); });
// Конфигурация встроенного контейнера
$container['embedded']['parameter'] = 'bar';
// И его использование
$container['embedded']['object']->...;
Заключение
Управление зависимостями — одна из важнейших и в то же время трудных задач в разработке веб-приложений. Большинство фреймворков предлагают собственные решения данной проблемы. Однако в случае использования фреймворков без менеджера зависимостей или проектирования архитектуры приложения без фреймворков, в качестве DI контейнера я бы однозначно выбрал Pimple.
P.S. Примеры использования — перевод официального readme Pimple.
Автор: MoonGrate