Как добавить тег на объекты определенного типа в Symfony DI?

«Как добавить тег на объекты определенного типа в Symfony DI?» — вопрос из категории Symfony, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Symfony Dependency Injection есть несколько способов автоматически назначать теги сервисам.

1. Использование _instanceof в конфигурации services.yaml: Это основной метод для автоматической разметки всех сервисов, реализующих определенный интерфейс или наследующих класс.

services:
    _instanceof:
        AppServiceMessageHandlerInterface:
            tags: ['app.message_handler']

Все сервисы, реализующие MessageHandlerInterface, получат тег app.message_handler.

2. Использование атрибутов (начиная с Symfony 6.1): Можно помечать классы сервисов атрибутом #[AutoconfigureTag].

use SymfonyComponentDependencyInjectionAttributeAutoconfigureTag;

#[AutoconfigureTag('app.message_handler')]
class EmailHandler implements MessageHandlerInterface
{
    // ...
}

3. Создание Compiler Pass для сложной логики: Если нужна динамическая логика назначения тегов, создается кастомный CompilerPass.

use SymfonyComponentDependencyInjectionCompilerCompilerPassInterface;
use SymfonyComponentDependencyInjectionContainerBuilder;

class CustomTagCompilerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container): void
    {
        foreach ($container->findDefinitions() as $id => $definition) {
            $class = $definition->getClass();
            if ($class && is_subclass_of($class, SomeBaseClass::class)) {
                $definition->addTag('custom.tag');
            }
        }
    }
}

Этот CompilerPass нужно зарегистрировать в методе build() вашего Kernel.

4. Получение тегированных сервисов: В коде или в другом CompilerPass можно получить все сервисы с определенным тегом.

// В CompilerPass
$taggedServices = $container->findTaggedServiceIds('app.message_handler');

// В контроллере или сервисе через инжектированный ContainerBuilder
$handlers = $this->container->get('service_container')->findTaggedServiceIds('app.message_handler');

Почему это важно: Автоматическое тегирование избавляет от ручной регистрации каждого сервиса в конфигурации, делает систему более масштабируемой и соответствует принципу Convention Over Configuration.