Pull to refresh

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

Reading time4 min
Views20K
Удивительно, что на Хабре всё ещё нет статей об этом гениальном 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.
Tags:
Hubs:
+10
Comments35

Articles

Change theme settings