2

I'm trying to symfony Configuration to determine how to wire some tagged services, but am running into problems. I've tried to boil the problem down into as little code as possible to help explain, but still - sorry for the length!

Here's the configuration part of things...

class Configuration implements ConfigurationInterface
{
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder('acme_test');
        $treeBuilder
            ->getRootNode()
            ->addDefaultsIfNotSet()
            ->children()
                ->scalarNode('email_sender')->isRequired()->end()
                ->scalarNode('email_title')->defaultValue('Notification email')->end()
            ->end();

        return $treeBuilder;
    }
}
acme_test:
    email_sender: '[email protected]'

I've also got an arbitrary service which takes $emailSender and $emailTitle. It's been tagged with the 'arbitrary-services' tags in services.yaml:

class ArbitraryService
{
    protected $emailSender;

    protected $emailTitle;

    public function __construct(string $emailSender, string $emailTitle)
    {
        $this->emailSender = $emailSender;
        $this->emailTitle = $emailTitle;
    }
}
services:
    App\Acme\TestBundle\ArbitraryService:
        tags: ['arbitrary-services']

The Symfony documentation tells me to do my tagged services finding in a CompilerPass - fair enough:

class AcmeTestCompilerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container)
    {
        $configs = $container->getExtensionConfig('acme_test');

        $taggedServices = $container->findTaggedServiceIds('arbitrary-services');

        // Great - got the tagged service definitions
        // Now going to wire things passed via config into those services (e.g. via binding)

        dump('From CompilerPass:');
        dump($taggedServices);
        dump($configs);
    }
}

In my full use-case, I want to tag different directories of services to cause them to receive different params based upon config options (e.g. services tagged with inject.alpha get parameters based upon config keys in acme -> alpha).

The problem tho' is that at this point, whilst the tagged services return fine, the configuration has not been processed, and so we're missing the email_title key which has a defaultValue:

wobble@pop-os:~/test$ bin/console c:c
"From CompilerPass:"
array:1 [
  "App\Acme\TestBundle\ArbitraryService" => array:1 [
    0 => []
  ]
]
array:1 [
  0 => array:1 [
    "email_sender" => "[email protected]"
  ]
]

If I check within the Extension / load() class on the other hand...

class AcmeTestExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);

        $taggedServices = $container->findTaggedServiceIds('arbitrary-services');

        dump('From load()');
        dump($taggedServices);
        dump($config);
    }
}

I'll find that I have the fully-processed configuration (yey!) but no tagged services as presumably I'm too early for that (hence the docs telling me to use a CompilerPass):

wobble@pop-os:~/test$ bin/console c:c
"From load()"
[]
array:2 [
  "email_sender" => "[email protected]"
  "email_title" => "Notification email"
]

Can anyone please tell me if it's possible to get hold of both the processed configuration and the tagged services at the same time? Thanks!

I've pushed the test code that reproduces my problem up on a github repo here: https://github.com/WubbleWobble/symfony-stack-overflow-1

2
  • 1
    This is basically a dup of stackoverflow.com/questions/15325146/…. It is a bit hacky but the idea is to store the processed config in your extension class and then access it in your pass via getExtension. I tested it on your code and seems to work. The normal workflow is that you convert the config data into parameters in the extension class and access them accordingly downstream. So make sure you really need this sort of unusual functionality. Commented Nov 15, 2019 at 14:28
  • Yeah - that's the conclusion we came to too - we could move the compiler pass inside the extension and then had shared state. I did get the sense I was swimming against the tide tho' - will have another think about things and make sure my use-case is sensible! Thanks :) Commented Nov 15, 2019 at 14:34

1 Answer 1

0

Managed to figure a solution. Moved the compiler pass inside the extension class and then shared the processed config across to it via a class property :)

class AcmeTestExtension extends Extension implements CompilerPassInterface
{
    /** @var array */
    protected $processedConfig;

    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $this->processedConfig = $this->processConfiguration($configuration, $configs);
    }

    public function process(ContainerBuilder $container)
    {
        $taggedServices = $container->findTaggedServiceIds('arbitrary-services');

        // Great - got the tagged service definitions
        // Now going to wire things passed via config into those services (e.g. via binding)

        dump('From CompilerPass:');
        dump($taggedServices);
        dump($this->processedConfig);
    }
}
"From CompilerPass:"
array:2 [
    "App\Acme\TestBundle\ArbitraryService" => array:1 [
        0 => []
    ]
]
array:2 [
    "email_sender" => "[email protected]"
    "email_title" => "Notification email"
]
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.