Типы Dependency Injection (внедрение зависимости) в сервис-контейнере Symfony.

Делая зависимости класса явными и требуя их явного внедрения в него, это хороший способ сделать класс более пригодным для повторного использования, тестирования и более легким для инстанцирования.

Существует несколько способов внедрения зависимостей. Каждый из способов Dependency Injection имеет свои преимущества и недостатки, а также иметюся различные способы работы с ними при использовании сервис контейнера.

Внедрение зависимостей через конструктор.

Наиболее распространенный способ внедрения зависимостей — через конструктор класса. Для этого вам нужно добавить аргумент в сигнатуру конструктора, чтобы принять зависимость:

namespace App\Mail;

// ...
class NewsletterManager
{
    private $mailer;

    public function __construct(MailerInterface $mailer)
    {
        $this->mailer = $mailer;
    }

    // ...
}

Вы можете указать, какой сервис вы хотите внедрить в конструктор другого класса, это в конфигурации сервис-контейнера Symfony:

# config/services.yaml
services:
    # ...

    App\Mail\NewsletterManager:
        arguments: ['@mailer']

Тайп-хинт для внедренного объекта означает, что вы можете быть уверены, что была введена подходящая зависимость. Благодар подсказке по типу вы сразу же получите явную ошибку, если будет введена неподходящая зависимость. Подсказка типов с использованием интерфейса, а не класса, позволяет сделать выбор зависимостей более гибким. И, предполагая, что вы используете только методы, определенные в интерфейсе, вы можете добиться этой гибкости и при этом безопасно использовать объект.

Есть несколько прекрасных преимуществ использования инъекции зависимосте в конструктор класса:

  • Если зависимость является обязательным требованием и класс не может работать без нее, то внедрение зависимости через конструктор гарантирует, что она присутствует, когда класс используется, поскольку инстанс класса не может быть создан без нее.
  • Конструктор вызывается только один раз при создании объекта, поэтому вы можете быть уверены, что зависимость не изменится в течение всего времени существования объекта.

Эти преимущества также означают, что внедрение в конструктор не подходит для работы с необязательными зависимостями. Его также сложнее использовать в сочетании с иерархиями классов: если класс использует инъекцию конструктора, расширение и переопределение конструктора становится проблематичным.

Immutable-setter Injection.

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

// ...
use Symfony\Component\Mailer\MailerInterface;

class NewsletterManager
{
    private $mailer;

    /**
     * @required
     * @return static
     */
    public function withMailer(MailerInterface $mailer)
    {
        $new = clone $this;
        $new->mailer = $mailer;

        return $new;
    }

    // ...
}

Чтобы использовать этот тип инъекции, не забудьте настроить его:

 # config/services.yaml
services:
     # ...

     app.newsletter_manager:
         class: App\Mail\NewsletterManager
         calls:
             - [withMailer, ['@mailer'], true]

Если вы решите использовать autowiring Symfony, то для этого типа внедрения зависимостей необходимо добавить статический докблок @return, чтобы контейнер мог зарегистрировать метод.

Этот подход полезен, если вам нужно настроить сервис в соответствии с вашими потребностями, поэтому вот преимущества immutable-setters:

  • Неизменяемые сеттеры работают с необязательными зависимостями, поэтому, если вам не нужна зависимость, сеттер не нужно вызывать.
  • Подобно внедрению в конструктор, использование неизменяемых установщиков заставляет зависимость оставаться неизменной в течение всего срока службы.
  • Этот тип инъекций хорошо работает с особенностями, так как сервис может быть составлен, таким образом, адаптация сервиса к требованиям вашего приложения проще.
  • Сеттер может вызываться несколько раз, таким образом, добавление зависимости в коллекцию становится проще и позволяет добавлять переменное число зависимостей.

Setter Injection.

Другая возможность внедрения зависимости в класс — это добавление зависимости через метод сетер, который принимает и устанавливает зависимость:

// ...
class NewsletterManager
{
    private $mailer;

    public function setMailer(MailerInterface $mailer)
    {
        $this->mailer = $mailer;
    }

    // ...
}

# config/services.yaml
services:
    # ...

    app.newsletter_manager:
        class: App\Mail\NewsletterManager
        calls:
            - [setMailer, ['@mailer']]

На этот раз преимуществами являются:

  • Инъекция сеттера хорошо работает с необязательными зависимостями. Если вам не нужна зависимость, не вызывайте сеттер.
  • Вы можете вызвать сеттер несколько раз. Это особенно полезно, если метод добавляет зависимость в коллекцию. Затем вы можете иметь переменное количество зависимостей.
  • Как и в случае с неизменяемым установщиком, этот тип инъекций хорошо работает с трейтами и позволяет вам составлять свой сервис.

Недостатками инъекции зависимости через сеттер являются:

  • Сеттер может быть вызван не только во время создания, поэтому вы не можете быть уверены, что зависимость не будет заменена во время существования объекта (за исключением явного написания метода сеттера, чтобы проверить, был ли он уже вызван).
  • Вы не можете быть уверены, что установщик будет вызван, и поэтому вам нужно добавить проверки, что любые необходимые зависимости введены.

Property Injection

Другой возможностью является установка открытых полей класса напрямую:

// ...
class NewsletterManager
{
    public $mailer;

    // ...
}

# config/services.yaml
services:
    # ...

    app.newsletter_manager:
        class: App\Mail\NewsletterManager
        properties:
            mailer: '@mailer'

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

  • Вы не можете контролировать, когда зависимость установлена вообще, ее можно изменить в любой момент времени жизни объекта.
  • Вы не можете использовать подсказку типа, поэтому вы не можете быть уверены, какая зависимость внедрена, кроме как путем записи в код класса для явного тестирования экземпляра класса перед его использованием.

Но полезно знать, что такой способ внедрения зависимости через публичные свойства можно сделать с помощью сервис-контейнера, особенно если вы работаете с кодом, который находится вне вашего контроля, например, в сторонней библиотеке, которая использует открытые свойства для своих зависимостей.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *