New in Symfony 6.4: AutowireLocator and AutowireIterator Attributes



Contributed by Kevin Bond
and Nicolas Grekas
in #51392
and #51832.

Sometimes, services need access to several other services without being sure
that all of them will actually be used. Injecting all services can hurt
performance (because Symfony will instantiate all of them, even unused ones)
and injecting the entire container is strongly discouraged in Symfony applications.
The best solution in those cases is to use service subscribers and locators.
A service locator is like a custom service container that only includes the
services that you selected.
In Symfony 6.4 we're improving service locators so you can also define them
using PHP attributes
instead of configuration files. The new #[AutowireLocator]
attribute takes a single service ID or an array of service IDs as its first argument:

use App\CommandHandler\BarHandler;
use App\CommandHandler\FooHandler;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\Attribute\AutowireLocator;

class SomeService
{
public function __construct(
#[AutowireLocator([FooHandler::class, BarHandler::class])]
private ContainerInterface $handlers,
) {
}

public function someMethod(): void
{
$fooService = $this->handlers->get(FooHandler::class);
}
}

You can also define aliases for these services and even include optional services
by prefixing the service class with a ? symbol:

use App\CommandHandler\BarHandler;
use App\CommandHandler\FooHandler;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\Attribute\AutowireLocator;
use Symfony\Contracts\Service\Attribute\SubscribedService;

class SomeService
{
public function __construct(
#[AutowireLocator([
'foo' => FooHandler::class,
'bar' => new SubscribedService(type: 'string', attributes: new Autowire('%some.parameter%')),
'optionalBaz' => '?'.BazHandler::class,
])]
private ContainerInterface $handlers,
) {
}

public function someMethod(): void
{
$fooService = $this->handlers->get('foo');

if ($this->handlers->has('optionalBaz')) {
// ...
}
}
}

Check out the #[AutowireLocator] source to learn about its other arguments,
such as $indexAttribute, $defaultPriorityMethod, $exclude, etc.
If you prefer to receive an iterable instead of a service locator, replace
the AutowireLocator attribute by AutowireIterator.

Sponsor the Symfony project.