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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
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
.
Thank you for this great feature !
You don't even know how long I've been waiting for such a solution. Thank you!
Oh wow ! Thanks. Nice feature, really !
First look: brain hurts. Second look: brain is soothed.
That's great!
Amazing! Thank you!