Типи впрвадження

Дата оновлення перекладу 2024-06-05

Типи впрвадження

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

Існує декілька способів того, як можуть бути впроваджені залежності. Кожна точка впровадження має свої переваги та недоліки, які варто врахувати, так само як і різні способи працювати з ними при використанні сервіс-контейнера.

Впровадження конструктора

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

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/Mail/NewsletterManager.php
namespace App\Mail;

// ...
class NewsletterManager
{
    public function __construct(
        private MailerInterface $mailer,
    ) {
    }

    // ...
}

Ви можете вказати, який сервіс ви хочете впровадити у нього, у конфігурації сервіс-контейнера:

1
2
3
4
5
6
# config/services.yaml
services:
    # ...

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

Tip

Додавання підказки впровадженого обʼєкта означає, що ви можете бути впевнені, що була впроваджена правильна залежність. Шляхом додавання підказок ви отримаєте чітку помилку одраз ж, у випадку якщо впроваджується неправильна залежність. Додаючи підказки, використовуючи інтерфейс, а не клас, ви робите вибір залежності гнучкішим. І якщо припустити, що ви використовуєте лише методи, визначні в інтерфейсі, ви можете отримати цю гнучкість і все одно безпечно використовувати обʼєкт.

Існує декілька переваг використання впровадження конструктора:

  • Якщо залежність є вимогою і клас не може працювати без неї, тоді впровадження через конструктор гарантує, що вона буде присутня, коли буде використано клас, так як клас не може бути створений без неї.
  • Конструктор викликається тільки один раз, коли створюється обʼєкт, так що ви можете бути впевнені, що залежність не зміниться під час життєвого циклу обʼєкта.

Ці переваги означають, що впровадження конструкторра не підходить для роботи з опціональними залежностями. Також його складніше використовувати у комбінації з ієрархією класів: якщо клас використовує впровадження конструктора, то розширення та перевизначення конструктора стає ускладненим.

Впровадження незмінного сетера

Ще одним можливим впровадженням є використання методу, який повертає окремий екземпляр шляхом клонування оригінального сервісу; цей підхід дозволяє вам робити сервіс незмінним:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// src/Mail/NewsletterManager.php
namespace App\Mail;

// ...
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Contracts\Service\Attribute\Required;

class NewsletterManager
{
    private MailerInterface $mailer;

    /**
     * @return static
     */
    #[Required]
    public function withMailer(MailerInterface $mailer): self
    {
        $new = clone $this;
        $new->mailer = $mailer;

        return $new;
    }

    // ...
}

Для того, щоб використати цей тип впровадження, не забудьте його сконфігурувати:

1
2
3
4
5
6
7
8
# config/services.yaml
services:
     # ...

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

Note

Якщо ви вирішите використати автомонтування, цей тип впровадження вимагає від вас додавання @return static у докблок для того, щоб контейнер міг зареєструвати метод.

Цей підхід корисний, якщо вам потрібно сконфігурувати ваш сервіс відповідно до ваших потреб, тому, ось переваги незмінних сетерів:

  • Незмінні сетери працюють з опціональними залежностями, таким чином, якщо залежність вам не потрібна, сетер не має викликатися.
  • Як і впровадження конструктора, використання незмінних сетерів змушує залежність залишатися такою ж під час життєвого циклу сервісу.
  • Цей тип впровадження добре працює з рирсами, так як сервіси можуть бути створені таким чином, адаптування сервісу до вимог вашого додатку стає легшим.
  • Сетер може бути викликаний багато разів, таким чином, додавання залежності у колекцію стає легшим та дозволяє вам додавати змінну кількість залежностей.

Недоліки:

  • Так як виклик сетера опціональний, залежність може бути null під час виклику методів сервісу. Ви повинні перевірити, щоб залежність була доступна, перед її використанням.
  • Якщо сервіс не оголошено лінивим, він несумісний з сервісами, що посилаються одне на одного у тому, що ми називаємо циклічними петлями.

Впровадження сетера

Іншою можливою точкою впровадження у клас є додавання методу сетера, який приймає залежність:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Mail/NewsletterManager.php
namespace App\Mail;

use Symfony\Contracts\Service\Attribute\Required;

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

    #[Required]
    public function setMailer(MailerInterface $mailer): void
    {
        $this->mailer = $mailer;
    }

    // ...
}
1
2
3
4
5
6
7
8
# config/services.yaml
services:
    # ...

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

Цього разу переваги такі:

  • Впровадження сетера добре працює з опціональними залежностями. Якщо вам не потрібна залежність - не викликайте сетер.
  • Ви можете викликати сетер багато разів. Це особливо корисно, якщо метод додає залежність до колекції. Потім ви зможете мати змінну кількість залежностей.
  • Як і впровадження незмінного сетера, цей тип впровадження добре працює з рисами та дозволяє вам створювати ваш сервіс.

Недоліки впровадження сетера:

  • Сетер може бути викликаний більше одного разу, а також задовго після ініціалізації, тому ви не можете бути впевнені, що залежність не була замінена під час життєвого циклу обʼєкта (якщо метод сетера не був написаний чітко, щоб перевіряти, чи його вже було викликано).
  • Ви не можете бути впевнені, що сетер буде викликано, тому вам потрібно додавати перевірки того, чи були впроваджені якісь з обовʼязкових залежностей.

Впровадження властивості

Ше одна можливість - просто встановити публічні поля класу напряму:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Mail/NewsletterManager.php
namespace App\Mail;

use Symfony\Contracts\Service\Attribute\Required;

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

    #[Required]
    public function setMailer(MailerInterface $mailer): void
    {
        $this->mailer = $mailer;
    }

    // ...
}
1
2
3
4
5
6
7
8
# config/services.yaml
services:
    # ...

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

В основному у впровадження властивостей існують лише недоліки, воно схоже на впровадження сетера, але з наступними додатковими та важливими проблемами:

  • Ви взагалі не можете контролювати, коли встановлюється залежність, вона може бути змінена у будь-який момент життєвого циклу обʼєкта.

Однак корисно знати, що це може бути зроблено з сервіс-контейнером, особливо якщо ви працюєте з кодом, який непідвладний вам, як, наприклад, стороння бібліотека, яка використовує публічні властивості для своїх залежностей.