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?
That's a great improvment. Is it possible to use it with the PHP service definition?
@Florent yes it is, by setting an AbstractArgument object as the value of the argument (which is exactly what loaders are doing).
@Florent
Yep! Behind the scenes, this works via a new AbstractArgumentClass:
Cheers!
Great!
Does this combine correctly with unnamed service arguments? Take your Generator as example, how would you statically pass the maker.file_manager?
maker.generator: class: Symfony\Bundle\MakerBundle\Generator arguments: - '@maker.file_manager' $rootNamespace: !abstract defined in MakerPass
or
maker.generator: class: Symfony\Bundle\MakerBundle\Generator arguments: $fileManager: '@maker.file_manager' $rootNamespace: !abstract defined in MakerPass