New in Symfony 5.4: DependencyInjection Improvements

Inject tagged services in service locators

Contributed by
Ion Bazan
in #43015.

In Symfony 5.4 you can use tagged iterators as arguments of service locators,
which simplifies the injection of tagged services in other services. The following
example shows how to use this feature when using YAML config (it works with XML
and PHP config too):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# config/services.yaml
services:
_instanceof:
AppCommandHandlerInterface:
tags: [‚app.command_handler‘]

app.command_handlers:
class: SymfonyComponentDependencyInjectionServiceLocator
arguments: [!tagged_iterator { tag: ‚app.command_handler‘, default_index_method: ‚getCommandName‘ }]

AppCommandBus:
arguments: [‚@app.command_handlers‘]

AppAnotherCommandBus:
arguments: [‚@app.command_handlers‘]

Autowire union and intersection types

Contributed by
Nicolas Grekas
in #43479.

PHP 8.0 added union types (e.g. ClassA | ClassB $variable) and PHP 8.1 adds
intersection types (ClassA & ClassB $variable). In Symfony 5.4 we’ve improved
the DependencyInjection component to support both. For example:

1
2
3
4
public function __construct(NormalizerInterface & DenormalizerInterface $serializer)
{
// …
}

Autowiring of intersection types works when both types have a corresponding
autowiring alias, and if both aliases point to the very same service.

New config options for TaggedIterator and TaggedLocator attributes

Contributed by
Mykhailo Shtanko
in #43386.

When using tagged iterators and locators for your services, you can define
tagged services with priority and tagged services with index. In Symfony 5.4
we’ve improved the TaggedIterator and TaggedLocator attributes so you
can define those options in the attributes too:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use PsrContainerContainerInterface;
use SymfonyComponentDependencyInjectionAttributeTaggedIterator;
use SymfonyComponentDependencyInjectionAttributeTaggedLocator;

public function __construct(
#[TaggedIterator(tag: ‚…‘, defaultPriorityMethod: ‚getPriority‘)]
private iterable $argument,
)
{
}

public function __construct(
#[TaggedLocator(tag: ‚…‘, defaultIndexMethod: ‚getDefaultFooName‘)]
private ContainerInterface $argument,
)
{
}

Added a new SubscribedService attribute

Contributed by
Kevin Bond
in #42238.

In previous Symfony versions, the ServiceSubscriberTrait could be used to
inject services in some of your class methods. This trait looks for all methods
in your class that have no arguments and a return type to provide a
ServiceLocator for the services of those return types.

This behavior works well most of the times, but in some cases it’s common to
have such a method (no arguments but a return type) which shouldn’t get any
services injected. That’s why in Symfony 5.4 we’ve deprecated that behavior
and introduced a new SubscribedService PHP attribute.

Add that attribute to any method in which you want a service injected:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use PsrLogLoggerInterface;
use SymfonyComponentRoutingRouterInterface;
use SymfonyContractsServiceAttributeSubscribedService;
use SymfonyContractsServiceServiceSubscriberInterface;

class MyService implements ServiceSubscriberInterface
{
#[SubscribedService]
private function router(): RouterInterface
{
return $this->container->get(__METHOD__);
}

#[SubscribedService]
private function logger(): LoggerInterface
{
return $this->container->get(__METHOD__);
}

// …
}

Autoconfigurable methods, properties and parameters

Contributed by
Ruud Kamphuis
in #42039.

In Symfony you can autoconfigure classes annotated with attributes using some
code like the following in your dependency injection extension:

1
2
3
4
5
6
$container->registerAttributeForAutoconfiguration(
MyAttribute::class,
static function (ChildDefinition $definition, MyAttribute $attribute, ReflectionClass $reflector): void {
$definition->addTag(‚my_tag‘, [’some_property‘ => $attribute->someProperty]);
}
);

In Symfony 5.4 we’ve improved this feature to allow you to also autoconfigure
methods, properties and parameters. First, make sure that you have some PHP
attribute defined in your application that allows using them on methods and properties.

Now, use that PHP attribute in some method and/or property:

1
2
3
4
5
6
7
8
#[MyAttribute]
class MyService {
}

class MyOtherService {
#[MyAttribute]
public function myMethod() {}
}

You can now use the registerAttributeForAutoconfiguration() method in your
extension, together with a union of the types that you want to search for.
In this example, the extension only cares for classes and methods, so it uses
ReflectionClass|ReflectionMethod $reflector:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
final class MyBundleExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container) : void
{
$container->registerAttributeForAutoconfiguration(
MyAttribute::class,
static function (ChildDefinition $definition, MyAttribute $attribute, ReflectionClass|ReflectionMethod $reflector) : void {
$args = [];
if ($reflector instanceof ReflectionMethod) {
$args[‚method‘] = $reflector->getName();
}
$definition->addTag(‚my.tag‘, $args);
}
);
}
}
Sponsor the Symfony project.

Symfony Blog
Read More