In some Symfony applications is common to get all services tagged with a specific tag. The traditional solution was to create a compiler pass, find those services and iterate over them. However, this is overkill when you just need to get those tagged services. That's why in Symfony 3.4, we've added a shortcut to achieve the same result without having to create that compiler pass.
When using YAML configuration, add the !tagged nameOfTag
notation in the
arguments
property of any service to inject all the services tagged with the
given tag. For example, to inject all Twig extensions:
1 2 3
services:
App\Manager\TwigManager:
arguments: [!tagged twig.extension]
Now you can get those Twig extensions in your service and iterate over them:
1 2 3 4 5 6 7 8 9 10
// src/App/Manager/TwigManager.php
namespace App\Manager;
class TwigManager
{
public function __construct(iterable $twigExtensions)
{
// ...
}
}
If you prefer XML configuration instead of YAML, use the following syntax:
1 2 3 4 5
<services>
<service id="App\Manager\TwigManager">
<argument type="tagged" tag="twig.extension" />
</service>
</services>
Finally, if you need to get the tagged services in a specific order, use the
priority
attribute on the tagged services.
This example will work only with PHP 7.1 because
iterable
as a type hint appeared only in this PHP version.@Vladislav that's correct! But don't forget that PHP 7.1 is not the future, but the present (and required to run Symfony 4 for example). In fact, active support for PHP 7.1 ends on December 1st 2018, so we just have 1 more year to enjoy PHP 7.1 (source: http://php.net/supported-versions.php)
Amazing! The only simpler solution is if php had generics:
public function __construct(array < Twig_ExtensionInterface >)
One can wish for :)
Just what we were waiting for ! Good job !
Is there also a way yo deal with custom attributes or do you stil need a compiler pass for that?
great! was always super boring make the compiler pass
@Cliff you'll need a compiler pass for that because the only attribute supported is "priority".
Would be nice if TwigManager's constructor could look like:
public function __construct(TwigExtension ...$twigExtensions)
@Kevin a variadic argument would not be compatible with the lazy-loading feature of the IteratorArgument. Btw, this is why the typehint is iterable in the example, not array. It won't be injected as an array.
That's nice! Is the array just indexed from 0 to n or is the key the services name?
!tagged
reads as "not tagged", given!
is used as "not" in a lot of programming languages, including php :-S@Ivan Yep, that's also how I read it at first. Maybe it's named after "!important" in CSS, though? :)
The ! syntax comes from Yaml, that's what the spec names as Yaml tags (not Symfony's tags)
Amazing, thank you for this feature!
Would it be possible to use type syntax as well?
There is no syntax highlighter, so here is the link to gist with example: https://gist.github.com/TomasVotruba/5518df9beb30afe84267c6ca80311de2
That would be super awesome :)
See this and think, its so obvious, why wasn't it always in Symfony!
Is this somehow possible with something less than php 7.1? Does it work without a typehint in the constructor?
@Craig yes and yes, it's completely independent from type-hints
Why not using RewindableGenerator directly instead of iterable?
Just answered my own question: Because you might pass array directly, especially for testing.