Контейнер IoC — неочевидная на первый взгляд возможность Laravel: его описание в документации сбивает с толку многих начинающих программистов и какое-то короткое время я сам был одним из них. Однако после копания этой темы и при поддержке замечательного сообщества Laravel на IRC-канале FreeNode #laravel эта тема полностью прояснилась. Надеюсь, я смогу пролить немного света на этот таинственный аспект Laravel в этой статье.
IoC означает «обратный контроль» («Inversion of Control»). Я не буду усложнять изложение детальным описанием этой проблемы, есть множество статей на эту тему. Вместо этого будем относиться к IoC как к «выполнение наоборот» или «возврат контроля выполнения кода в Laravel» для того, чтобы создать нужный объект.
На самом деле это всё, зачем нужен данный контейнер — создание объектов. Если не считать его применения для определения зависимостей для юнит-тестов (мы поговорим об этом позже) вы можете просто думать о нём, как о короткой форме создания сложных объектов или получения объекта-одиночки («singleton») без прямого вызова методов самого класса. Об одиночках мы поговорим чуть ниже, а пока посмотрим, как происходит регистрация объектов в контейнере IoC.
\
Регистрация объектов
Используем нашу фантазию: представим класс PHPDiscoball
, который мы будем использовать во всех примерах ниже для всякого рода трюков.
К сожалению, наш PHPDiscoball
требует установить множество конфигурационных опций перед тем, как он может быть использован. Посмотрим, как это происходит:
$db = new Discoball(Discoball::SHINY);
$db->configure_shinyness('max');
$db->spin_speed('8900rpm');
Ого, как много настроек! Ещё несколько таких вызовов и нам надоест каждый раз создавать и настраивать этот дискотечный шар («discoball»). Давайте попросим IoC делать это для нас, а сами перейдём непосредственно к коду.
Я обычно помещаю этот код в application/start.php, но вы можете делать это где угодно до тех пор, пока ваши объекты вызываются уже после того, как они зарегистрированы в контейнере IoC.
IoC::register('discoball', function () {
// создаём экземпляр объекта, как в примере выше:
$db = new Discoball(Discoball::SHINY);
$db->configure_shinyness('max');
$db->spin_speed('8900rpm');
// возвращаем созданный объект:
return $db;
});
Мы вызываем метод PHPIoC::register()
для регистрации нашего объекта в контейнере. Первый параметр — строка, которая будет использована для создания объекта — я использовал слово «discoball», потому что это выглядит самым логичным. Второй параметр — анонимная функция («closure»), которая и создаёт сам объект.
Внутри функции вы видите уже знакомый код настройки шара. Функция возвращает новый экземпляр объекта.
Отлично! Объект зарегистрирован и это всё, что делает IoC... шучу. Давайте посмотрим, как нам теперь получить новый объект.
Получение объектов
Теперь когда у нас есть зарегистрированный объект PHPDiscoball
нужно понять, как нам получить его обратно. Вот код для этого:
$db = IoC::resolve('discoball');
Вот и всё! Вместо создания и настройки нового экземпляра объекта в каждом месте нашего кода мы вызываем метод PHPIoC::resolve()
, передаём ему строку-идентификатор, которая была использована при регистрации и IoC вызовет анонимную функцию, привязанную к нему, которая вернёт нам новый экземпляр объекта PHPDiscoball
.
Одиночки (singletons)
Получение объекта — это хорошо, но что делать, если наш дискотечный шар требует много ресурсов для поддержания каждого экземпляра и нам нужно сконструировать его только один раз? В этом случае метод PHPIoC::register
не будет нам полезен, так как он создаёт новый объект при каждом запросе. Здесь в дело вступают одиночки.
Шаблон проектирования «одиночка» (Singleton pattern) означает, что вы пишите свой класс таким образом, что их экземпляр создаётся неким статическим методом, который будет возвращать один и тот же экземпляр объекта при каждом последующем вызове. Таким образом, класс создаётся только один раз на время выполнения запроса.
Вы можете легко найти больше информации по этой теме — например, в документации по PHP.
Одиночки полезны, но они требуют определённой структуры класса, чтобы работать. Контейнер IoC имеет метод PHPsignleton()
, который решает эту проблему, так как с ним не требуется специальный статический метод класса для создания экземпляра объекта. Давайте зарегистрируем наш PHPDiscoball
в качестве одиночки:
IoC::singleton('discoball', function () {
// создаём экземпляр объекта, как в примерах выше:
$db = new Discoball(Discoball::SHINY);
$db->configure_shinyness('max');
$db->spin_speed('8900rpm');
// возвращаем созданный объект:
return $db;
});
Как вы видите, код ничем не отличается от кода регистрации, если не считать того, что вместо метода PHPIoC::register()
используется PHPIoC::singleton()
с теми же параметрами.
Когда мы попытаемся получить экземпляр дискотечного шара с помощью PHPIoC::resolve()
анонимная функция будет вызвана только один раз, после чего возвращённый объект будет сохранён в контейнере и все последующие вызовы PHPresolve()
будут возвращаеть именно его. Например:
// здесь анонимная функция вызывается и создаёт новый экземпляр объекта:
$db = IoC::resolve('discoball');
// на этот раз функция не вызывается и мы получаем тот же объект, что и строкой выше:
$another = IoC::resolve('discoball');
Замечательно! Нужно отметить, что в качестве второго параметра вы можете передать методу PHPIoC::singleton()
уже созданный объект и он будет возвращаться всеми последующими вызовами PHPresolve()
. Например:
$db = new Discoball(Discoball::SHINY);
IoC::singleton('discoball', $db);
// получить наш дискотечный шар:
$ball = IoC::resolve('discoball');
В следующий раз я расскажу, как внедрять зависимости в контейнер IoC для проведения юнит-тестов.