Pimple? Не… Не слышал

в 9:01, , рубрики: dependency injection, php, переводы, метки: ,

Удивительно, что на Хабре всё ещё нет статей об этом гениальном 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js