Просмотр поста

.
Delphinum
Инъекция зависимостей
Инъекция зависимостей или DI, довольно спорный, но очень удобный механизм. Здесь я опишу лишь одну из реализаций DI, но встречаются и другие. В общем смысле DI это такой механизм, который позволяет по имени члена класса или аргумента функции получить конкретный сервис.

Рассмотрим несложный пример. Предположим у нас есть контроллер, в котором реализуется пара методов:
<?php
class NewsController{
  public function getAction(){
    ...
    $user = User::getCurrentUser();
    $userId = $user->getId();
    ...
  }

  public function saveAction(){
    ...
    $log = ServiceLocator::get('log');
    $log->info(...);
    ...
  }
}

Не имеет значение что именно делают эти методы, важно лишь то, что в них мы напрямую обращаемся к некоторым службам, а именно получает экземпляр текущего пользователя и службу логгирования. Чем плоха такая реализация? Ее довольно тяжело тестировать, так как нам придется не просто подменить используемые службы специальными объектами-заглушками, но и как то передать их в тестируемые методы. Решить эту проблему довольно просто:
<?php
class NewsController{
  private $currentUser;
  private $log;

  public function __construct($currentUser, $log){
$this->currentUser = $currentUser;
$this->log = $log;
  }

  public function getAction(){
    ...
    $userId = $this->currentUser->getId();
    ...
  }

  public function saveAction(){
    ...
    $this->log->info(...);
    ...
  }
}

Как видно, достаточно просто вынести логику доступа к зависимостям за пределы класса. Другими словами классу не важно откуда берутся его зависимости, они лишь устанавливаются ему через конструктор и им же используются. Тестировать такой класс становится намного проще, но вот инстанциировать, сложнее. Теперь, чтобы получить экземпляр этого класса не достаточно просто использовать операцию new, нужно так же разрешить все зависимости контроллера. Как раз эту задачу решает DI.

Пакет Bricks.Di реализует два механизма разрешения зависимостей:
1. Зависимости контроллера - это те зависимости, которые рассмотрены в примере выше
2. Зависимости вызываемого метода - это любые зависимости, представленные в виде имен аргументов вызываемого метода
Как правило, этого вполне достаточно для большинства задач, для которых применяется DI.

Данный пакет представлен классом Bricks\Di\Manager, который реализован в виде декоратора над локатором служб, массивом, или любым классом, реализующим интерфейс ArrayAccess стандартной библиотеки PHP. Локатор используется DI для получения целевых зависимостей, а сам менеджер реализует механизм выявления этих зависимостей в методах класса. Работает он достаточно просто, потому одного примера должно быть достаточно:
use Bricks\ServiceLocator\Manager as ServiceLocator;
use Bricks\Di\Manager as Di;

// Подготовка локатора служб.
$locator = new ServiceLocator;
$locator->set('currentUser', User::getCurrentUser());
$locator->set('log', new Log);

// Оборачивание локатора в инъектор зависимостей.
$di = new Di($locator);

// Разрешение зависимостей конструктора контроллера и получение его экземпляра.
$controller = $di->constructInjection('NewsController');

Все довольно просто, не правда ли? Сначала формируется локатор служб, затем он оборачивается в DI, а после вызывается конструктор контроллера через механизм DI, который разрешает все его зависимости (на основании имен аргументов конструктора). На выходе мы получаем экземпрял контроллера с разрешенными зависимостями. Это похоже на узкоспециализированную фабрику объектов.

Как уже было сказано ранее, пакет так же позволяет разрешать зависимости вызываемых методов по аналогии с конструктором. Делается это аналогично просто:
use Bricks\ServiceLocator\Manager as ServiceLocator;
use Bricks\Di\Manager as Di;

class NewsController{
  ...

  public function deleteAction($cache){
    ...
    $cache->remove(...);
  }
}

$locator = new ServiceLocator;
...
$locator->set('cache', new Cache);

$di = new Di($locator);

$controller = $di->constructInjection('NewsController');
$result = $di->methodInjection($controller, 'deleteAction');

Здесь выполняется та же самая операция инъекции зависимостей, но не для создания экземпляра класса, а для вызова метода с разрешением его зависимостей.

Архив с примером.