Пример создания и использования сервисов с кастомными тегами и атрибутами в Symfony

Создаем CompilerPass в котором получаем список сервисов помеченых кастомным тегом acme.filter_provider и регистрируем их в registry сервисе:

<?php

namespace Acme\Bundle\AcmeBundle\DependencyInjection\CompilerPass;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Reference;

/**
 * Class FilterProviderRegistryCompilerPass.
 *
 * @package Acme\Bundle\AcmeBundle\DependencyInjection\CompilerPass
 */
class FilterProviderRegistryCompilerPass implements CompilerPassInterface
{
    public const PROVIDER_REGISTRY_SERVICE = 'acme.registry.filter_registry';
    public const PROVIDER_TAG = 'acme.filter_provider';

    /**
     * {@inheritDoc}
     */
    public function process(ContainerBuilder $container): void
    {
        if (!$container->hasDefinition(self::PROVIDER_REGISTRY_SERVICE)) {
            return;
        }

        $registryDefinition = $container->getDefinition(self::PROVIDER_REGISTRY_SERVICE);

        foreach ($this->getTaggedServiceIds($container) as $providerId => $providerTags) {
            foreach ($providerTags as $providerTag) {
                $registryDefinition->addMethodCall(
                    'registerProvider',
                    [new Reference($providerId), $providerTag['entity'], $providerTag['code']]
                );
            }
        }
    }

    /**
     * Gets all services tagged with "acme.filter_provider".
     *
     * @param ContainerBuilder $container The container.
     *
     * @return array An array contains services.
     */
    protected function getTaggedServiceIds(ContainerBuilder $container): array
    {
        $providers = $container->findTaggedServiceIds(self::PROVIDER_TAG);

        foreach ($providers as $serviceId => $tags) {
            foreach ($tags as $tag) {
                $this->assertTagHasAttributes($serviceId, self::PROVIDER_TAG, $tag, ['entity', 'code']);
            }
        }

        return $providers;
    }

    /**
     * Validates tag attributes for the given service ID.
     *
     * @param string $serviceId The service ID.
     * @param string $tagName The tag name to validate.
     * @param array $tagAttributes The list of attributes of the given tag.
     * @param array $requiredAttributes The list of required attributes of the given tag.
     *
     * @throws LogicException
     */
    protected function assertTagHasAttributes(
        string $serviceId,
        string $tagName,
        array $tagAttributes,
        array $requiredAttributes
    ): void {
        foreach ($requiredAttributes as $attribute) {
            if (!empty($tagAttributes[$attribute])) {
                continue;
            }

            throw new LogicException(
                sprintf('Tag "%s" for service "%s" must have attribute "%s"', $tagName, $serviceId, $attribute)
            );
        }
    }
}

Далее добавляем созданный CompilerPass в контейнер:

<?php

namespace Acme\Bundle\AcmeBundle;

use Acme\Bundle\AcmeBundle\DependencyInjection\CompilerPass\FilterProviderRegistryCompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

/**
 * Class AcmeBundle.
 *
 * @package Acme\Bundle\AcmeBundle
 */
class AcmeBundle extends Bundle
{

    /**
     * {@inheritdoc}
     */
    public function build(ContainerBuilder $container): void
    {
        parent::build($container);

        $container->addCompilerPass(new FilterProviderRegistryCompilerPass());
    }
}

Создаем класс для registry сервиса:

<?php

namespace Acme\Bundle\AcmeBundle\Registry;

use Acme\Bundle\AcmeBundle\Provider\FilterProviderInterface;
use LogicException;
use UnexpectedValueException;

/**
 * Class FilterProviderRegistry.
 *
 * @package Acme\Bundle\AcmeBundle\Registry
 */
class FilterProviderRegistry
{

    /**
     * Providers storage format:
     * [
     *      '<entityClass>' => <providerObject>,
     * ]
     *
     * @var FilterProviderInterface[]
     */
    protected $providers = [];

    /**
     * Registers a given provider.
     *
     * @param FilterProviderInterface $provider The filter provider.
     * @param string $entityClass The entity class.
     * @param string $code The filter code.
     *
     * @throws LogicException
     */
    public function registerProvider(
        FilterProviderInterface $provider,
        string $entityClass,
        string $code
    ): void {
        if (!empty($this->providers[$entityClass])) {
            throw new LogicException(sprintf('Provider with entity class "%s" already exists', $entityClass));
        }

        $provider->setEntityClass($entityClass);
        $provider->setCode($code);

        $this->providers[$entityClass] = $provider;
    }

    /**
     * Checks if a provider is registered for the given entity class.
     *
     * @param string $entityClass The entity class.
     *
     * @return bool True if registered.
     */
    public function hasProvider(string $entityClass): bool
    {
        return !empty($this->providers[$entityClass]);
    }

    /**
     * Gets a provider by the entity class.
     *
     * @param string $entityClass The entity class.
     *
     * @return FilterProviderInterface The filter provider.
     *
     * @throws UnexpectedValueException
     */
    public function getProvider(string $entityClass): FilterProviderInterface
    {
        if (!$this->hasProvider($entityClass)) {
            throw new UnexpectedValueException(sprintf('Provider with entity class "%s" is not exist', $entityClass));
        }

        return $this->providers[$entityClass];
    }

    /**
     * Gets a list of providers.
     *
     * @return FilterProviderInterface[] The list of registered providers.
     */
    public function getProviders(): array
    {
        return $this->providers;
    }
}

В services.yml добавляем registry сервис:

acme.registry.filter_registry:
    class: Acme\Bundle\AcmeBundle\Registry\FilterProviderRegistry
    public: false

Создаем интерфейс для сервисов, которые будут добавляться в registry сервис:

<?php

namespace Acme\Bundle\AcmeBundle\Provider;

/**
 * Interface FilterProviderInterface.
 *
 * @package Acme\Bundle\AcmeBundle\Provider
 */
interface FilterProviderInterface
{

    /**
     * Sets an entity class for this provider.
     *
     * @param string $entityClass The entity class.
     */
    public function setEntityClass(string $entityClass): void;

    /**
     * Gets an entity class of this provider.
     *
     * @return string The entity class.
     */
    public function getEntityClass(): string;

    /**
     * Sets a code for this provider.
     *
     * @param string $code The code.
     */
    public function setCode(string $code): void;

    /**
     * Gets a code of this provider.
     *
     * @return string The code.
     */
    public function getCode(): string;

    /**
     * Outputs concatenated class and code.
     */
    public function echo(): void;
}

Создаем класс для провайдера:

<?php

namespace Acme\Bundle\AcmeBundle\Provider;

/**
 * Class ApplicationFilterProvider.
 *
 * @package Acme\Bundle\AcmeBundle\Provider
 */
class ApplicationFilterProvider implements FilterProviderInterface
{

    /**
     * @var string The entity class.
     */
    protected $entityClass;

    /**
     * @var string The code.
     */
    protected $code;

    /**
     * {@inheritDoc}
     */
    public function setEntityClass(string $entityClass): void
    {
        $this->entityClass = $entityClass;
    }

    /**
     * {@inheritDoc}
     */
    public function getEntityClass(): string
    {
        return $this->entityClass;
    }

    /**
     * {@inheritDoc}
     */
    public function setCode(string $code): void
    {
        $this->code = $code;
    }

    /**
     * {@inheritDoc}
     */
    public function getCode(): string
    {
        return $this->code;
    }

    /**
     * {@inheritDoc}
     */
    public function echo(): void
    {
        echo $this->getEntityClass() . ':' . $this->getCode();
    }
}

В services.yml добавляем созданный провайдер: 

acme.provider.application_filter:
    class: Acme\Bundle\AcmeBundle\Provider\ApplicationFilterProvider
    public: false
    tags:
        - name: acme.filter_provider
          entity: Acme\Bundle\AcmeBundle\Entity\Application
          code: applications

Использование:

$registry = $container->get('acme.registry.filter_registry');

$registry->getProvider('Acme\Bundle\AcmeBundle\Entity\Application')->echo();
Benya