Как работат с тегами сервисов¶
Так же, как запись блога в сети может быть тегирован такими тегами, как "Symfony" или "PHP", сервисы, сконфигурированные в вашем контейнере тоже могут быть тегированы. В сервис-контейнере тег подразумевает, что сервис должен быть использован для какой-то конкретной цели. Возьмём следующий пример:
- YAML
1 2 3 4 5
# app/config/services.yml services: AppBundle\Twig\AppExtension: public: false tags: [twig.extension]
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13
<!-- app/config/services.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="AppBundle\Twig\AppExtension" public="false"> <tag name="twig.extension" /> </service> </services> </container>
- PHP
1 2 3 4 5 6
// app/config/services.php use AppBundle\Twig\AppExtension; $container->register('app.twig_extension', AppExtension::class) ->setPublic(false) ->addTag('twig.extension');
Тег twig.extension
- это специальный тег, который TwigBundle использует
во время конфигурации. Назначив сервису этот тег twig.extension
, пакет
знает, что сервис AppExtension::class
должен быть зарегистрирован в Twig
как расширение Twig. Другими словами, Twig находит все сервисы, тегированные
twig.extension
и автоматически регистрирует их как расширения.
Потом теги становятся способом сообщить Symfony или другим стороним пакетам. что ваш сервис должен быть зарегистрирован или как-то особенно использован пакетом.
Чтобы получить список всех доступных в базовом фреймворке Symfony тегов, смотрите
Built-in Symfony Service Tags. Каждый из них имеет своё влияние на ваш сервис и многие
теги требуют дополнительных аргументов (кроме простого параметра name
).
Для большинства пользователей - это всё, что вам нужно знать. Если вы хотите углубиться в изучение того, как создать ваши собственные пользовательские теги, то продолжайте читать.
Автоконфигурация тегов¶
Начиная с Symfony 3.3, если вы включите автоконфигурацию,
то некоторые теги применяются для вас самостоятельно. Это применимо к тегу twig.extension
:
контейнер видит, что ваш класс раширяет Twig_Extension
(или, точнее, что он реализует
Twig_ExtensionInterface
) и добавляет тег за вас.
Tip
Чтобы применить тег ко всем вашим автоматически сконфигурированным сервисам,
расширяющим класс или интерфейс, вызовите метод
registerForAutoconfiguration()
в extension или из вашего ядра:
1 2 3 4 5 6 7 8 9 10 11 12 | // app/AppKernel.php
class AppKernel extends Kernel
{
// ...
protected function build(ContainerBuilder $container)
{
$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/AppBundle/Mail/TransportChain.php
namespace AppBundle\Mail;
class TransportChain
{
private $transports;
public function __construct()
{
$this->transports = array();
}
public function addTransport(\Swift_Transport $transport)
{
$this->transports[] = $transport;
}
}
|
Потом, определите цепочку как сервис:
- YAML
1 2
services: AppBundle\Mail\TransportChain: ~
- XML
1 2 3 4 5 6 7 8 9 10
<?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="AppBundle\Mail\TransportChain" /> </services> </container>
- PHP
1 2 3
use AppBundle\Mail\TransportChain; $container->autowire(TransportChain::class);
Определеите сервисы с пользовательским тегом¶
Теперь вы можете захотеть, чтобы несколько из классов \Swift_Transport
были инстанциированы и добавлены в цепочку автоматически, используя метод
addTransport()
. Например, вы можете добавить следующие транспорты как
сервисы:
- YAML
1 2 3 4 5 6 7
services: Swift_SmtpTransport: arguments: ['%mailer_host%'] tags: [app.mail_transport] Swift_SendmailTransport: tags: [app.mail_transport]
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="Swift_SmtpTransport"> <argument>%mailer_host%</argument> <tag name="app.mail_transport" /> </service> <service class="\Swift_SendmailTransport"> <tag name="app.mail_transport" /> </service> </services> </container>
- PHP
1 2 3 4 5 6
$container->register(\Swift_SmtpTransport::class) ->addArgument('%mailer_host%') ->addTag('app.mail_transport'); $container->register(\Swift_SendmailTransport::class) ->addTag('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/AppBundle/DependencyInjection/Compiler/MailTransportPass.php
namespace AppBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
use AppBundle\Mail\TransportChain;
class MailTransportPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
// всегда вначале проверяйте, определён ли первичный сервис
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', array(new Reference($id)));
}
}
}
|
Зарегистрируйте пропуск в контейнере¶
Для того, чтобы запустить пропуск компилятора, когда контейнер будет скомпилирован,
вам нужно добавить пропуск в контейнер в методе build()
вашего пакета:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // src/AppBundle/AppBundle.php
// ...
use Symfony\Component\DependencyInjection\ContainerBuilder;
use AppBundle\DependencyInjection\Compiler\MailTransportPass;
class AppBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
$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 | class TransportChain
{
private $transports;
public function __construct()
{
$this->transports = array();
}
public function addTransport(\Swift_Transport $transport, $alias)
{
$this->transports[$alias] = $transport;
}
public function getTransport($alias)
{
if (array_key_exists($alias, $this->transports)) {
return $this->transports[$alias];
}
}
}
|
Как вы видите, когда вызывается addTransport()
, требуется не только объект
Swift_Transport
, но также дополнительное имя строки для этого транспорта.
Тогда как вы можете разрешить каждому тегированному транспортному сервису также
снабжать дополнительное имя?
Чтобы ответить на этот вопрос, измените объявление сервиса:
- YAML
1 2 3 4 5 6 7 8 9
services: Swift_SmtpTransport: arguments: ['%mailer_host%'] tags: - { name: app.mail_transport, alias: smtp } Swift_SendmailTransport: tags: - { name: app.mail_transport, alias: sendmail }
- XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="Swift_SmtpTransport"> <argument>%mailer_host%</argument> <tag name="app.mail_transport" alias="smtp" /> </service> <service id="Swift_SendmailTransport"> <tag name="app.mail_transport" alias="sendmail" /> </service> </services> </container>
- PHP
1 2 3 4 5 6
$container->register(\Swift_SmtpTransport::class) ->addArgument('%mailer_host%') ->addTag('app.mail_transport', array('alias' => 'foo')); $container->register(\Swift_SendmailTransport::class) ->addTag('app.mail_transport', array('alias' => 'bar'));
Tip
В формате YAML, вы можете представить тег в качестве простой строки, если вам не нужно указывать дополнительные атрибуты. Следующие определения являются эквивалентными.
1 2 3 4 5 6 7 8 9 10 11 12 | services:
# синтаксис Compact
Swift_SendmailTransport:
class: \Swift_SendmailTransport
tags: [app.mail_transport]
# синтаксиси Verbose
Swift_SendmailTransport:
class: \Swift_SendmailTransport
tags:
- { name: app.mail_transport }
|
New in version 3.3: Поддержка компактных нотаций тега в формате YAML была представлена в Symfony 3.3.
Отметьте, что вы добавили общий ключ 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\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
class TransportCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
// ...
foreach ($taggedServices as $id => $tags) {
// сервис может иметь один и тот же тег дважды
foreach ($tags as $attributes) {
$definition->addMethodCall('addTransport', array(
new Reference($id),
$attributes["alias"]
));
}
}
}
}
|
Двойной цикл может быть запутанным. Это потому, что сервис может иметь
больше одного тега. Вы тегируете сервис дважды или более с помощью тега
app.mail_transport
. Второй цикл foreach повторяет набор тегов
app.mail_transport
для текущего сервиса и даёт вам атрибуты.
Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.