Компонент DependencyInjection

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

Компонент DependencyInjection

Компонент DependencyInjection реалізує сервіс-контейнер, сумісний з PSR-11, який дозволяє вам стандартизувати та централізувати те, як будуються об'єкти у вашому додатку.

Для того, щоб зануритися у впровадження залежностей та сервіс-контейнери, див. Сервіс-контейнер.

Установка

1
$ composer require symfony/dependency-injection

Note

Якщо ви встановлюєте цей компонент поза додатком Symfony, вам потрібно підключити файл vendor/autoload.phpу вашому коді для включення механізму автозавантаження класів, наданих Composer. Детальніше можна прочитати у цій статті.

Базове застосування

See also

Ця стаття пояснює, як використовувати функції DependencyInjection як незалежного компоненту в будь-якому додатку PHP. Прочитайте статтю Сервіс-контейнер для розуміння того, як використовувати його в додатках Symfony.

У вас може бути простий клас на кшталт наступного Mailer, який ви хочете зробити доступним у якості сервісу:

1
2
3
4
5
6
7
8
9
10
11
class Mailer
{
    private string $transport;

    public function __construct()
    {
        $this->transport = 'sendmail';
    }

    // ...
}

Ви можете зареєструвати це в контейнері, як сервіс:

1
2
3
4
use Symfony\Component\DependencyInjection\ContainerBuilder;

$container = new ContainerBuilder();
$container->register('mailer', 'Mailer');

Покращенням класу для підвищення його гнучкості буде дозволити контейнеру встановлювати використаний transport. Якщо ви зміните клас так, щоб це передавалося до конструктору:

1
2
3
4
5
6
7
8
9
class Mailer
{
    public function __construct(
        private string $transport,
    ) {
    }

    // ...
}

То ви зможете встановлювати вибір транспорту в контейнері:

1
2
3
4
5
6
use Symfony\Component\DependencyInjection\ContainerBuilder;

$container = new ContainerBuilder();
$container
    ->register('mailer', 'Mailer')
    ->addArgument('sendmail');

Цей клас тепер набагато гнучкіший, так як ви відокремили вибір транспорту від реалізації, та помістили його в контейнер.

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

1
2
3
4
5
6
7
use Symfony\Component\DependencyInjection\ContainerBuilder;

$container = new ContainerBuilder();
$container->setParameter('mailer.transport', 'sendmail');
$container
    ->register('mailer', 'Mailer')
    ->addArgument('%mailer.transport%');

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

1
2
3
4
5
6
7
8
9
class NewsletterManager
{
    public function __construct(
        private \Mailer $mailer,
    ) {
    }

    // ...
}

При визначенні сервісу newsletter_manager, сервіс mailer ще не існує. Використовуйте клас Reference, щоб повідомити контейнеру, що необхідно впровадити сервіс mailer, коли він ініціалізує менеджер повідомлень новин:

1
2
3
4
5
6
7
8
9
10
11
12
13
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

$container = new ContainerBuilder();

$container->setParameter('mailer.transport', 'sendmail');
$container
    ->register('mailer', 'Mailer')
    ->addArgument('%mailer.transport%');

$container
    ->register('newsletter_manager', 'NewsletterManager')
    ->addArgument(new Reference('mailer'));

Якщо NewsletterManager не вимагав Mailer, і його впровадження не було обов'язковим, то ви можете використовувати впровадження сеттеру:

1
2
3
4
5
6
7
8
9
10
11
class NewsletterManager
{
    private \Mailer $mailer;

    public function setMailer(\Mailer $mailer): void
    {
        $this->mailer = $mailer;
    }

    // ...
}

Ви також можете тепер обрати не впроваджувати Mailer в NewsletterManager. Якщо ж ви хочете зробити це, то контейнер може викликати сеттер-метод:

1
2
3
4
5
6
7
8
9
10
11
12
13
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

$container = new ContainerBuilder();

$container->setParameter('mailer.transport', 'sendmail');
$container
    ->register('mailer', 'Mailer')
    ->addArgument('%mailer.transport%');

$container
    ->register('newsletter_manager', 'NewsletterManager')
    ->addMethodCall('setMailer', [new Reference('mailer')]);

Потім ви можете отримати ваш сервіс newsletter_manager з контейнеру таким чином:

1
2
3
4
5
6
7
use Symfony\Component\DependencyInjection\ContainerBuilder;

$container = new ContainerBuilder();

// ...

$newsletterManager = $container->get('newsletter_manager');

Отримання неіснуючих сервісів

За замовчуванням, коли ви намагаєтеся отримати неіснуючий сервіс, ви бачите виключення. Ви можете перевизначити цю поведінку наступним чином:

1
2
3
4
5
6
7
8
9
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;

$containerBuilder = new ContainerBuilder();

// ...

// другий аргумент опціональний і визначає, що робити, якщо сервіс не існує
$newsletterManager = $containerBuilder->get('newsletter_manager', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);

Ось всі можливі види поведінки:

  • ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE: викликає виключення під час компіляції (це поведінка за замовчуванням);
  • ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE: викликає виключення під час прогону при спробі отримати доступ до неіснуючого сервісу;
  • ContainerInterface::NULL_ON_INVALID_REFERENCE: повертає null;
  • ContainerInterface::IGNORE_ON_INVALID_REFERENCE: ігнорує команду-обгортку, яка запитує посилання (наприклад, ігнорувати сеттер, якщо сервіс не існує);
  • ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE: ігнорує/повертає null для

невикористаних сервісів або невалідних посилань.

Уникнення залежності вашого коду від контейнеру

Незважачи на те, що ви можете добувати сервіси з контейнеру напряму, краще це мінімізувати. Наприклад, у випадку з NewsletterManager, в який ви впровадили сервіс mailer, замість того, щоб зробити запит до нього з контейнеру. Ви могли впровадити контейнур, а потім добути з нього сервіс mailer, але він був би прив'язаний до цього конкретного контейнеру, що ускладнило б повторне використання класу будь-де.

Рано чи пізно, вам необхідно буде отримати сервіс з контейнеру, але це має бути зроблено мінімальну кількість разів у точці входу вашого додатку.

Установка контейнера з файлами конфігурації

Окрім установки сервісів з використанням PHP, як описано вище, ви також можете використовувати файли конфігурації. Це дозволяє вам використовувати XML або YAML для написання описів для серверів, а не використовувати PHP для визначення сервісів, як у прикладі вище. В будь-яких випадках, окрім найменших додатків, логічно впорядкувати визначення сервісу, перемістивши їх в один або більше файлів конфігурації. Щоб зробити це, вам також знадобиться встановити компонент Config.

Завантаження файлу конфігурації XML:

1
2
3
4
5
6
7
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;

$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.xml');

Завантаження файлу конфігурації YAML:

1
2
3
4
5
6
7
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.yaml');

Note

Якщо ви хочете завантажити файли конфігурації YAML, то вам також знадобиться встановити компонент Yaml.

Tip

Якщо ваш застосунок використовує нетрадиційні розширення файлу (наприклад, ваші файли XML мають розширення .config), то ви можете передати тип файлу в якості другого необов'язкового параметру методу load():

1
2
// ...
$loader->load('services.config', 'xml');

Якщо ви хочете використовувати PHP, щоб створювати сервіси, то ви можете перемістити це в окремий файл конфігурації, та завантажувати його подібним чином:

1
2
3
4
5
6
7
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;

$container = new ContainerBuilder();
$loader = new PhpFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.php');

Тепер ви можете встановлювати сервіси newsletter_manager та mailer, з використанням файлів конфігурації:

1
2
3
4
5
6
7
8
9
10
11
12
parameters:
    # ...
    mailer.transport: sendmail

services:
    mailer:
        class:     Mailer
        arguments: ['%mailer.transport%']
    newsletter_manager:
        class:     NewsletterManager
        calls:
            - [setMailer, ['@mailer']]

Дізнайтеся більше