На протяжении всего курса, мы говорили про процесс инициализации, внутри которого создаются необходимые объекты. Затем эти объекты используются в прикладном коде. К таким объектам относятся: соединение с базой данных, логгер, кеш, шаблонизатор, интеграция с внешними сервисами и многое другое.
В простых ситуациях, там где объектов немного и мы сами управляем кодом (а не используем фреймворки, определяющие структуру), зависимости можно внедрять "руками":
<?php
$geolocationService = new GoogleMaps();
$storeService = new StoreService($geolocationService);
$app = new App(['storeService' => $storeService]);
$app->run();
В коде, который активно использует инверсию зависимостей, инициализация кода выглядит как матрёшка из объектов. Объекты верхнего уровня принимают на вход объекты с нижних слоёв, шаг за шагом собирая готовое приложение.
С ростом числа объектов, процесс сборки усложняется и становится утомительным. Появляются ситуации, в которых объекты нужны в самой глубине кода, но прокинуть их можно только пройдя множество уровней. В популярных фреймворках инициализация может включать в себя создание и внедрение сотен объектов. Страшно даже представить, что придётся писать этот код вручную.
Для решения данной задачи, используется два подхода (шаблона проектирования). Ниже поговорим про каждый из них
Локатор (Service Locator)
Самый простой способ внедрять зависимости в таких системах - сервис локатор. Это объект, который содержит внутри себя все зависимости. Любой объект, которому нужен какой-либо сервис, обращается за ним к сервис локатору.
Возьмём для примера микрофреймворк Slim. У него есть расширение PHP-View, которое добавляет шаблонизатор в фреймворк. Попробуем внедрить это расширение через Service Locator:
<?php
use Slim\Factory\AppFactory;
use Slim\Views\PhpRenderer;
$app = AppFactory::create();
// Для простоты локатор реализован как обычный массив
// Но, в общем случае, он может быть чем угодно
$locator = [
'renderer' => new PhpRenderer('./templates')
];
$app->get('/hello/{name}', function ($request, $response, $args) use ($locator) {
// Прикладной код _знает_ про локатор и пользуется им для извлечения зависимостей
return $locator['renderer']->render($response, 'hello.php', $args);
});
$app->run();
Вот такой нехитрый подход для внедрения зависимостей. По нему написаны сотни статей со всевозможными вариациями его создания и использования. В большинстве из них локатор рассматривается как антипаттерн. Так как прикладной код знает про его существование.
Контейнер (DI Container)
Вершиной эволюции инверсии зависимостей считается DI Container. Продвинутые контейнеры, это целые фреймворки, которые занимаются инициализацией приложения, собирают необходимые объекты и прокидывают их друг в друга. В некоторых экосистемах, контейнер – центральная часть всей системы, которая занимается её оркестрацией (управлением). В Java, например, это Spring Framework. Он с лёгкостью может собирать и веб-приложение и демонов.
Ключевое отличие контейнера от локатора в том, что зависимости из контейнера попадают внутрь приложения прозрачно. Прикладной код не догадывается о существовании контейнера, он лишь видит объекты, которых ждёт. Для инъекции этих зависимостей используются стандартные подходы: либо через конструктор, либо через сеттеры и аргументы методов.
Снова посмотрим на Slim и интеграцию с PHP-View, но уже через встроенный контейнер:
<?php
use Slim\Factory\AppFactory;
use DI\Container; // PHP-DI
$container = new Container();
$container->set('renderer', function () {
return new \Slim\Views\PhpRenderer(__DIR__ . '/../templates');
});
AppFactory::setContainer($container);
$app = AppFactory::create();
$app->get('/hello/{name}', function ($request, $response, $args) {
return $this->get('renderer')->render($response, 'hello.php', $args);
});
$app->run();
Обработчик запроса получает renderer
через метод get
. Он не знает ничего про контейнер и про то, как зависимость попала внутрь $this
.
В PHP контейнеры имеют настолько важную роль, что их интерфейс был унифицирован через PSR-11. Для более подробного погружения в тему, посмотрите на PHP-DI.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.