Islam Israfilov
Contributed by Islam Israfilov in #35076

Sometimes, when defining services in your Symfony applications, there are arguments that can't be added in config files. The reason is that their values can only be calculated at runtime in a compiler pass or bundle extension.

In those cases, it's common to add an empty argument with some comment explaining that the value will be injected later. For example, the second argument of this service is the full list of directories where Twig templates are stored. That list is only available when running the application, because bundles can add their own directories too:

1
2
3
4
5
<service id="twig.template_iterator" class="Symfony\Bundle\TwigBundle\TemplateIterator">
    <argument type="service" id="kernel" />
    <argument type="collection" /> <!-- Twig paths -->
    <argument>%twig.default_path%</argument>
</service>

This other service needs the root namespace of the application, something that's better to calculate dynamically when running the application (instead of forcing the user to configure this value manually):

1
2
3
4
<service id="maker.generator" class="Symfony\Bundle\MakerBundle\Generator">
    <argument type="service" id="maker.file_manager" />
    <argument /> <!-- root namespace -->
</service>

In Symfony 5.1 we've improved this config to replace the "empty argument + comment" by proper abstract service arguments. These are arguments whose values can only be calculated at runtime in compiler passes or bundle extensions.

This is how the previous example looks like when using abstract arguments:

1
2
3
4
<service id="maker.generator" class="Symfony\Bundle\MakerBundle\Generator">
    <argument type="service" id="maker.file_manager" />
    <argument type="abstract" key="$rootNamespace">defined in MakerPass</argument>
</service>

The key value defines the argument name in the service constructor and the value enclosed in <argument> ... </argument> is an optional comment about the argument. If you use YAML to define services, use this other syntax based on the !abstract keyword:

1
2
3
4
maker.generator:
    class: Symfony\Bundle\MakerBundle\Generator
    arguments:
        $rootNamespace: !abstract defined in MakerPass

This is the config needed when using PHP:

1
2
3
4
5
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;

$builder->register('maker.generator', Generator::class)
    ->setArgument('$rootNamespace', new AbstractArgument('defined in MakerPass'));

If you don't replace the value of the abstract arguments in some compiler pass or bundle extension, you'll see the following error message:

Argument "$rootNamespace" of service "maker.generator" is abstract (defined in MakerPass), did you forget to define it?

Published in #Living on the edge