Как работать с тегами сервисов
Дата обновления перевода 2022-02-01
Как работать с тегами сервисов
Теги сервисов - это способ сообщить Symfony или другим сторонним пакетам, что ваш сервис должен быть зарегистрирован каким-то особым образом. Возьмите следующий пример:
- YAML
- XML
- PHP
1 2 3 4
# config/services.yaml
services:
App\Twig\AppExtension:
tags: ['twig.extension']
Сервисы, с тегом twig.extension
собираются во время инициализации
TwigBundle и добавляются в Twig как расширения.
Другие теги используются для интеграции ваших сервисов в другие системы. Чтобы
увидеть все доступные теги в базовом фреймворке Symfony, посмотрите
Встроенные сервис-теги Symfony. Каждый из них имеет разные эффект на ваш сервис, и
многие теги требуют дополнительных аргументов (кроме параметра name
).
Для большинства пользователей - это всё, что вам нужно знать. Если вы хотите углубиться в изучение того, как создать ваши собственные пользовательские теги, продолжайте читать.
Автоконфигурация тегов
Если вы включили автоконфигурацию , тогда некоторые теги
применяются для вас автоматически. Это так для тега twig.extension
: контейнер видит,
что ваш клас расширяет AbstractExtension
(точнее, реализует ExtensionInterface
),
и добавляет тег для вас.
Если вы хотите применять теги автоматически для ваших собственных сервисов,
используйте опцию _instanceof
:
- YAML
- XML
- PHP
1 2 3 4 5 6 7 8
# config/services.yaml
services:
# эта конфигурация применяется только к сервисам, созданным этим файлом
_instanceof:
# сервисы, классы которых являются экземплярами CustomInterface будут тегированы автоматически
App\Security\CustomInterface:
tags: ['app.custom_tag']
# ...
Для более продвинутых потребностей, вы можете определить автоматические теги, используя метод registerForAutoconfiguration().
В приложении Symfony, вызовите этот метод в вашем классе ядра:
1 2 3 4 5 6 7 8 9 10 11 12
// src/Kernel.php
class Kernel extends BaseKernel
{
// ...
protected function build(ContainerBuilder $container): void
{
$container->registerForAutoconfiguration(CustomInterface::class)
->addTag('app.custom_tag')
;
}
}
В пакете Symfony, вызовите этот метод в методе load()
класса расширения пакета:
1 2 3 4 5 6 7 8 9 10 11 12
// src/DependencyInjection/MyBundleExtension.php
class MyBundleExtension extends Extension
{
// ...
public function load(array $configs, ContainerBuilder $container): void
{
$container->registerForAutoconfiguration(CustomInterface::class)
->addTag('app.custom_tag')
;
}
}
Создание пользовательских тегов
Теги сами по себе не изменяют функциональность ваших сервисов каким-либо образом. Но если вы захотите, вы можете попросить у строителя контейнера список всех сервисов, которые были тегированы каким-то конкретным тегом. Это полезно в пропусках компилятора, где вы можете найти эти сервисы и использовать либо изменять их каким-либо образом.
Например, если вы используете Swift Mailer, то вы можете представить, что вы
хотите реализовать "транспортную цепочку", которая является коллекцией классов,
реализующих \Swift_Transport
. Используя цепочку, вы захотите, чтобы Swift
Mailer попробовал несколько способов передачи сообщения, пока один из них не
сработает.
Для начала, определите класс TransportChain
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// src/Mail/TransportChain.php
namespace App\Mail;
class TransportChain
{
private $transports;
public function __construct()
{
$this->transports = [];
}
public function addTransport(\Swift_Transport $transport): void
{
$this->transports[] = $transport;
}
}
Затем, определите цепочку как сервис:
- YAML
- XML
- PHP
1 2 3
# config/services.yaml
services:
App\Mail\TransportChain: ~
Определеите сервисы с пользовательским тегом
Теперь вы можете захотеть, чтобы несколько из классов \Swift_Transport
были инстанциированы и добавлены в цепочку автоматически, используя метод
addTransport()
. Например, вы можете добавить следующие транспорты как
сервисы:
- YAML
- XML
- PHP
1 2 3 4 5 6 7 8
# config/services.yaml
services:
Swift_SmtpTransport:
arguments: ['%mailer_host%']
tags: ['app.mail_transport']
Swift_SendmailTransport:
tags: ['app.mail_transport']
Заметьте, что каждому сервису был предоставлен тег под названием app.mail_transport
.
Это пользовательский тег, который вы будете использовать в вашем пропуске компилятора.
Пропуск компилятора - это то, что придаёт этому тегу какой-то "смысл".
Создайте пропуск компилятора
Теперь вы можете использовать пропуск компилятора ,
чтобы запросить у контейнера любые сервисы с тегом app.mail_transport
:
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 26 27 28
// src/DependencyInjection/Compiler/MailTransportPass.php
namespace App\DependencyInjection\Compiler;
use App\Mail\TransportChain;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class MailTransportPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
// всегда вначале проверяйте, определён ли первичный сервис
if (!$container->has(TransportChain::class)) {
return;
}
$definition = $container->findDefinition(TransportChain::class);
// найти все ID сервисов с тегом app.mail_transport tag
$taggedServices = $container->findTaggedServiceIds('app.mail_transport');
foreach ($taggedServices as $id => $tags) {
// добавьте транспортный сервис в сервис ChainTransport
$definition->addMethodCall('addTransport', [new Reference($id)]);
}
}
}
Зарегистрируйте пропуск в контейнере
Для того, чтобы запустить пропуск компилятора, когда контейнер будет скомпилирован, вам нужно добавить пропуск компилятора в контейнер в расширение пакета или из вашего ядра:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// src/Kernel.php
namespace App;
use App\DependencyInjection\Compiler\MailTransportPass;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
// ...
class Kernel extends BaseKernel
{
// ...
protected function build(ContainerBuilder $container): void
{
$container->addCompilerPass(new MailTransportPass());
}
}
Tip
При реализации CompilerPassInterface
в расширении сервиса, вам не
нужно регистрировать его. Смотрите документацию компонентов ,
чтобы узнать больше информации.
Добавление дополнительных атрибутов в тег
Иногда вам будет нужна дополнительная информацию о каждом сервисе, который был тегирован вашим тегом. Например, вы можете захотеть добавить дополнительное имя каждому члену транспортной цепочки.
Для начала, измените класс TransportChain
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
class TransportChain
{
private $transports;
public function __construct()
{
$this->transports = [];
}
public function addTransport(\Swift_Transport $transport, $alias): void
{
$this->transports[$alias] = $transport;
}
public function getTransport($alias): ?\Swift_Transport
{
if (array_key_exists($alias, $this->transports)) {
return $this->transports[$alias];
}
return null;
}
}
Как вы видите, когда вызывается addTransport()
, требуется не только объект
Swift_Transport
, но также дополнительное имя строки для этого транспорта.
Тогда как вы можете разрешить каждому тегированному транспортному сервису также
снабжать дополнительное имя?
Чтобы ответить на этот вопрос, измените объявление сервиса:
- YAML
- XML
- PHP
1 2 3 4 5 6 7 8 9 10
# config/services.yaml
services:
Swift_SmtpTransport:
arguments: ['%mailer_host%']
tags:
- { name: 'app.mail_transport', alias: 'smtp' }
Swift_SendmailTransport:
tags:
- { name: 'app.mail_transport', alias: 'sendmail' }
Tip
В формате YAML, вы можете представить тег в качестве простой строки, если вам не нужно указывать дополнительные атрибуты. Следующие определения являются эквивалентными.
1 2 3 4 5 6 7 8 9 10 11 12
# config/services.yaml
services:
# Compact syntax
Swift_SendmailTransport:
class: \Swift_SendmailTransport
tags: ['app.mail_transport']
# Verbose syntax
Swift_SendmailTransport:
class: \Swift_SendmailTransport
tags:
- { name: 'app.mail_transport' }
Заметьте, что вы добавили общий ключ alias
к тегу. Чтобы действительно
использовать его, обновите компилятор:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class TransportCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
// ...
foreach ($taggedServices as $id => $tags) {
// сервис может иметь один и тот же тег дважды
foreach ($tags as $attributes) {
$definition->addMethodCall('addTransport', [
new Reference($id),
$attributes['alias'],
]);
}
}
}
}
Двойной цикл может быть запутанным. Это потому, что сервис может иметь
больше одного тега. Вы тегируете сервис дважды или более с помощью тега
app.mail_transport
. Второй цикл foreach повторяет набор тегов
app.mail_transport
для текущего сервиса и даёт вам атрибуты.
Ссылайтесь на тегированные сервисы
Symfony предоставляет сокращение для внедрения всех сервисов, тегированных конкретным тегом, что частно нужно в некоторых приложениях, чтобы вам не нужно было подключать пропуск компилятора только для этого.
В следующем примере все сервисы, тегированные app.handler
передаются как
первый аргумент конструктора сервису App\HandlerCollection
:
- YAML
- XML
- PHP
1 2 3 4 5 6 7 8 9 10 11 12
# config/services.yaml
services:
App\Handler\One:
tags: ['app.handler']
App\Handler\Two:
tags: ['app.handler']
App\HandlerCollection:
# внедрить все сервисы, тегированные app.handler в качестве первого аргумента
arguments:
- !tagged_iterator app.handler
После компиляции, сервис HandlerCollection
имеет возможность итерировать поверх
ваших обработчиков приложения:
1 2 3 4 5 6 7 8 9
// src/HandlerCollection.php
namespace App;
class HandlerCollection
{
public function __construct(iterable $handlers)
{
}
}
See also
Смотрите также тегированные сервисы локатора
Тегированные сервисы с приоритетностью
Тегированные сервисы могуть быть приоритизированы с использованием
атрибута priority
. Приоритетность - это положительное или отрицательное
целое число, которое по умолчанию равняется 0
. Чем выше число, тем раньше
будет найден тегированный сервис в коллекции:
- YAML
- XML
- PHP
1 2 3 4 5
# config/services.yaml
services:
App\Handler\One:
tags:
- { name: 'app.handler', priority: 20 }
Другой опцией. которая особенно полезна при использовании автоконфигурации тегов,
является реализация статического метода getDefaultPriority()
в самом сервисе:
1 2 3 4 5 6 7 8 9 10
// src/Handler/One.php
namespace App\Handler;
class One
{
public static function getDefaultPriority(): int
{
return 3;
}
}
Если вы хотите иметь другой метод, определяющие приоритетность
(например, getPriority()
вместо getDefaultPriority()
),
вы можете определить его в конфигурации сервиса сбора:
- YAML
- XML
- PHP
1 2 3 4 5 6
# config/services.yaml
services:
App\HandlerCollection:
# внедрить все сервисы с тегом app.handler в качестве первого аргумента
arguments:
- !tagged_iterator { tag: app.handler, default_priority_method: getPriority }
Тегированные сервисы с индексом
Если вы хотите извлечь конкретный сервис из внедренной коллекции, вы можете
использовать опции index_by
и default_index_method
аргумента, в сочетании
с !tagged_iterator
.
Используя предыдущий пример, эта конфигурация сервиса создает коллекцию,
проиндексированную по атрибуту key
:
- YAML
- XML
- PHP
1 2 3 4 5 6 7 8 9 10 11 12
# config/services.yaml
services:
App\Handler\One:
tags:
- { name: 'app.handler', key: 'handler_one' }
App\Handler\Two:
tags:
- { name: 'app.handler', key: 'handler_two' }
App\HandlerCollection:
arguments: [!tagged_iterator { tag: 'app.handler', index_by: 'key' }]
После компиляции, HandlerCollection
может итерировать поверх ваших обработчиков
приложения. Чтобы извлечь конкретный сервис из итератора, вызовите функцию
iterator_to_array()
, а затем используйте атрибут key
, чтобы получить элемент
массива. Например, чтобы извлечь обработчик handler_two
:
1 2 3 4 5 6 7 8 9 10 11 12
// src/Handler/HandlerCollection.php
namespace App\Handler;
class HandlerCollection
{
public function __construct(iterable $handlers)
{
$handlers = $handlers instanceof \Traversable ? iterator_to_array($handlers) : $handlers;
$handlerTwo = $handlers['handler_two'];
}
}
Tip
Как и с приоритетностью, вы можете также реализовать статический метод
getDefaultIndexName()
в обработчиках и опустить атрибут индекса (key
):
1 2 3 4 5 6 7 8 9 10 11
// src/Handler/One.php
namespace App\Handler;
class One
{
// ...
public static function getDefaultIndexName(): string
{
return 'handler_one';
}
}
Вы также можете определить имя статического метода, реализуемого в каждом
сервисе, с помощью атрибута default_index_method
в тегированном аргументе:
- YAML
- XML
- PHP
1 2 3 4 5 6 7
# config/services.yaml
services:
# ...
App\HandlerCollection:
# используйте getIndex() вместо getDefaultIndexName()
arguments: [!tagged_iterator { tag: 'app.handler', default_index_method: 'getIndex' }]