Компонент DependencyInjection (внедрение зависимости)
Дата обновления перевода: 2024-07-03
Компонент 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
- Как декорировать сервисы
- Локаторы сервисов
- Подписчики и локаторы сервисов
- Как определять необщие сервисы
- Как внедрять экземпляры в контейнер
- Как работать с тегами сервисов