Компонент 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']]
Дізнайтеся більше
- Компіляція контейнера
- Робочий процес подубдови контейнера
- Пояснення змін контейнера DI в Symfony 3.3 (autowiring, _defaults та ін.)
- Як створювати псевдоніми сервісу та відзначати сервіси як приватні
- Автоматичне визначення залежностей сервісу (автомонтування)
- Виклики методу сервісу та впровадження сетера
- Як працювати з передачами компілятора
- Як сконфігурувати сервіс за допомогою конфігуратора
- Як налагоджувати сервіс-контейнер та список сервісів
- Як працювати з обʼєктами визначень сервісу
- Як впроваджувати значення, засновані на складних виразах
- Використання фабрики для створення сервісів
- Як імпортувати файли/джерела конфігурації
- Типи впрвадження
- Ліниві сервіси
- Як зробити аргументи/посилання сервісів опціональними
- Вступ у параметри
- Як управляти спільними залежностями з батьківськими сервісами
- Як отримати запит з сервіс-контейнера
- Service Closures
- Як декораувати сервіси
- Локатори сервісів
- Підписники та локатори сервісів
- Як визначати не спільні сервіси
- Як впроваджувати екземпляри у контейнер
- Як працювати з тегами сервісів