PHP: Полиморфизм
Теория: Управление зависимостями
На протяжении всего курса, мы говорили про процесс инициализации, внутри которого создаются необходимые объекты. Затем эти объекты используются в прикладном коде. К таким объектам относятся: соединение с базой данных, логгер, кеш, шаблонизатор, интеграция с внешними сервисами и многое другое.
В простых ситуациях, там где объектов немного и мы сами управляем кодом (а не используем фреймворки, определяющие структуру), зависимости можно внедрять "руками":
В коде, который активно использует инверсию зависимостей, инициализация кода выглядит как матрёшка из объектов. Объекты верхнего уровня принимают на вход объекты с нижних слоёв, шаг за шагом собирая готовое приложение.
С ростом числа объектов, процесс сборки усложняется и становится утомительным. Появляются ситуации, в которых объекты нужны в самой глубине кода, но прокинуть их можно только пройдя множество уровней. В популярных фреймворках инициализация может включать в себя создание и внедрение сотен объектов. Страшно даже представить, что придётся писать этот код вручную.
Для решения данной задачи, используется два подхода (шаблона проектирования). Ниже поговорим про каждый из них
Локатор (Service Locator)
Самый простой способ внедрять зависимости в таких системах - сервис локатор. Это объект, который содержит внутри себя все зависимости. Любой объект, которому нужен какой-либо сервис, обращается за ним к сервис локатору.
Возьмём для примера микрофреймворк Slim. У него есть расширение PHP-View, которое добавляет шаблонизатор в фреймворк. Попробуем внедрить это расширение через Service Locator:
Вот такой нехитрый подход для внедрения зависимостей. По нему написаны сотни статей со всевозможными вариациями его создания и использования. В большинстве из них локатор рассматривается как антипаттерн. Так как прикладной код знает про его существование.
Контейнер (DI Container)
Вершиной эволюции инверсии зависимостей считается DI Container. Продвинутые контейнеры, это целые фреймворки, которые занимаются инициализацией приложения, собирают необходимые объекты и прокидывают их друг в друга. В некоторых экосистемах, контейнер – центральная часть всей системы, которая занимается её оркестрацией (управлением). В Java, например, это Spring Framework. Он с лёгкостью может собирать и веб-приложение и демонов.
Ключевое отличие контейнера от локатора в том, что зависимости из контейнера попадают внутрь приложения прозрачно. Прикладной код не догадывается о существовании контейнера, он лишь видит объекты, которых ждёт. Для инъекции этих зависимостей используются стандартные подходы: либо через конструктор, либо через сеттеры и аргументы методов.
Снова посмотрим на Slim и интеграцию с PHP-View, но уже через встроенный контейнер. Обработчик запроса получает renderer через метод get(). Он не знает ничего про контейнер и про то, как зависимость попала внутрь $this.
Помимо отсутствия глобального объекта в виде локатора, мы получаем еще ряд преимуществ, которые начинают играть роль с ростом приложения. Так как контейнер это объект, встроенный в процесс работы фреймворка, то он может связывать свою работу с его логикой:
- Поддерживать работу с синглтонами и пулом объектов. Можно управлять состоянием объектов, создавая их один раз и переиспользуя в течение жизни приложения или отдельного запроса.
- Интегрироваться с middleware и событиями. Во многих фреймворках контейнер связывается с системой middleware или событийной моделью, упрощая внедрение зависимостей в обработчики событий и промежуточные слои.
- Обеспечивать автоматическую резолвинг-зависимостей. В продвинутых контейнерах можно регистрировать классы без явного указания всех зависимостей — контейнер сам анализирует конструкторы и подставляет нужные объекты.
В PHP контейнеры имеют настолько важную роль, что их интерфейс был унифицирован через PSR-11.
.png)

