Drupal 8 Dependency Injection, Service Container и все такое

Перевод
Автор оригинала Danny Sipos
Оригинал Drupal 8 Dependency Injection, Service Container And All That Jazz
Drupal 8 Dependency Injection, Service Container и все такое

С переходом от процедурного стиля к ООП в Drupal 8, многие Drupal разработчики находились в небольшом ступоре от новой концепции. Я сам был в ужасе от таких понятий как Dependency Injection и Service Container, и это заставило меня побеспокоится о моем дальнейшем развитии в роли Drupal разработка.

В этой статье мы рассмотрим, что такое Dependency Injection и почему нужно использовать контейнер для управления этими зависимостями. В Symfony и Drupal 8 этот контейнер называется Service Container (потому что мы называем эти глобальные объекты сервисами). После этого мы кратко рассмотрим, как это все применяется в Drupal 8.

Что такое Dependency Injection?

В качестве примера, мы возьмем очень простой класс:

class Car {
 
  protected $engine;
 
  public function __construct() {
    $this->engine = new Engine();
  }
 
  /* ... */
 
}

Когда нам нужно создать новый экземпляр класса Car, мы пишем следующее:

$car = new Car();

Теперь у нас есть объект $car, у которого в свою очередь есть свойство $engine, содержащие объект другого класса. А что если нам нужен другой автомобиль, с другим двигателем? В этом случае нам нужно будет создать новый класс унаследованный от класса Car, в котором необходимо переопределить метод __construct(), с другим двигателем. Есть ли в этом смысл? Нет.

Теперь давайте посмотрим на немного модифицированный класс Car:

class Car {
 
  protected $engine;
 
  public function __construct($engine) {
    $this->engine = $engine;
  }
 
  /* ... */
 
}

В этом случае, чтобы создать экземпляр класса, нам нужно написать следующее:

$engine = new Engine();
$car = new Car($engine);

Уже лучше. Теперь чтобы создать новый авто, уже с другим двигателем, нам нужно просто передать объект двигателя при создании нового экземпляра авто.

$turbo = new TurboEngine();
$car2 = new Car($turbo);

Это и есть Dependecy Injection. Класс Car зависит от Engine, поэтому мы передаем его в конструктор. Если бы мы прописали Engine в классе Car, мы бы не смогли так просто заменить двигатель. Такие Сonstructor Injections наиболее распространены, но также можно найти и другие способы, например Setter Injection, с помощью котором мы бы устанавливали двигатель через setter метод.

А что делает контейнер?

Только что мы рассмотрели очень простой пример класса. А теперь представьте себе, что класс Car имеет множество других потенциально заменимых компонентов (зависимостей), таких как, коробка передач, тормоза или колеса. По идее нам нужно вручную инициализировать все необходимые объекты, только так мы сможем передать их тому, кому они нужны. Вот это то контейнер и делает за нас.

В принципе это работает следующим образом. Первым делом вы регистрируете в контейнере свои классы и зависимости. А потом в любом месте вашего приложения вы можете получить доступ к вашему контейнеру и запросить экземпляр определенного класса (или сервиса, как это называется в Symfony и Drupal 8). Контейнер создает экземпляр класса, а также инициализирует каждую зависимость, и возвращает вам объект сервиса. Но в чем разница между сервисами к которым мы имеет доступ через контейнер и другими PHP классами?

Очень хорошее определение их различия есть в Symfony Book:

Как правило, PHP объект является службой, если он используется в вашем приложении глобально. Единственная служба Mailer используется для отправки email сообщений, в то время как множество объектов Message, которые содержат эти сообщения, службами не являются. Точно так же, объект Product не является службой, но объект, который сохраняет Product в базу данных - является службой.

На мой взгляд, не нужно понимать то как работает контейнер под капотом. Достаточно знать, как регистрировать классы в контейнере и получать доступ к ним позже из приложения. На самом деле есть несколько способов зарегистрировать Service, но в Drupal 8 используются YAML файлы. В Symfony вы можете использовать непосредственно сам PHP, YAML или даже XML. Более подробно об этом вы можете прочитать на странице документации. Ну а для доступа к сервисам, можно воспользоваться двумя способами: статически и через Dependency Injection.

Статически это делается очень просто, для этого нам нужно воспользоваться глобальным пространством имен \Drupal для доступа к его методу service(), который возвращает сервис с именем, которое мы передали.

$service = \Drupal::service('my service');

Такой подход в основном используется когда сервис нам нужен в наших .module файлах, где мы все еще работаем с процедурным кодом. Если мы работаем с классом (таким как form, controller, entity и т. д.), то мы должны внедрять сервис как зависимость для класса. Я не буду расписывать по шагам, что нужно сделать чтобы внедрить зависимости в ваши классы в Drupal 8. Вы можете посмотреть вводную серию статей по разработке модуля для Drupal 8, где покрыт все процесс создания сервисов и их внедрение в качестве зависимостей.

Вывод

Dependency Injection - это очень простая концепция, которая практикует разделение функционала между классами. Передавая зависимости в объекты, мы можем изолировать их цели и легко подменять их другими объектами. Кроме того, это делает unit тестирование классов проще, и предоставляет возможность передавать в качестве зависимостей ложные объекты.

Service Container в основном управляет некоторыми классами, когда они становятся подавляющими. То есть когда количество классов растет, а также растет количество их зависимостей. Он отслеживает какие зависимости нужны сервису, перед тем как создать экземпляр, а все что вам нужно сделать это получить доступ к контейнеру, чтобы запросить этот сервис.

Надеюсь, что все понятно.

Комментарии

К сожалению, комментариев пока нет
Для того чтобы оставлять комментарии, вам необходимо авторизоваться. Вход