Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Управление зависимостями PHP: Полиморфизм

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

В простых ситуациях, там где объектов немного и мы сами управляем кодом (а не используем фреймворки, определяющие структуру), зависимости можно внедрять "руками":

<?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.


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 6 300 ₽ в месяц
Занимайтесь созданием сайтов, веб-приложений, сервисов и их интеграцией с внутренними бизнес-системами на бекенд-языке PHP
10 месяцев
с нуля
Старт 10 октября

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»