Symfony 4.3 introduced a new keyword to be used with its dependency injection container, that generates locators made from tagged services. It's easy to use and saves us from maintaining handwritten compiler passes and locator classes. Sadly, after a short announcement, there's little yet in the way of documentation, so I hope this article will help.
A new keyword to generate a service locator
Since Symfony 4.0, the keyword !tagged
allows referencing tagged service, but it is not
helpful if you need to access these services by key, because it provides a collection. Symfony 4.3
improved the feature with the keyword !tagged_locator
, which can be used to generate a service
locator built from tagged services. Also, !tagged
is now renamed as !tagged_iterator
.
The service locator implements PSR-11—the one with the container interface—and only instantiates
services when they are required, just like !tagged_iterator
. e.g. $locator->get('my.service')
. I
recommend you have a look at the dumped container, it's glorious.
Now, consider the following example, it's a listing of an API's actions, cunningly tagged with
action
. Because they are defined in a single file, we can define the tag as a default.
# config/presentation/actions.yaml
services:
_defaults:
autowire: true
tags: [ action ]
action.app.hello:
class: HelloFresh\MenuService\Presentation\HTTP\Action\App\HelloAction
action.menu.create:
class: HelloFresh\MenuService\Presentation\HTTP\Action\Menu\CreateAction
action.menu.update:
class: HelloFresh\MenuService\Presentation\HTTP\Action\Menu\UpdateAction
action.menu.delete:
class: HelloFresh\MenuService\Presentation\HTTP\Action\Menu\DeleteAction
Now, to group these actions in a service locator, ready to be consumed, we use the keyword
!tagged_locator
. The following example demonstrates how to generate an action locator made from
services tagged with action
.
Careful! You need v4.3.4 for there was a bug in the initial release.
# config/presentation/http.yaml
services:
HelloFresh\MenuService\Presentation\HTTP\Middleware\ActionMiddleware:
arguments:
$actionLocator: !tagged_locator { tag: action }
Indexing tagged services
By default, tagged services are indexed by their service identifier, but the index key can be specified with the tag definition or a static method.
Specify the index key with a tag
Use the tag definition to specify the index key and use index_by
to specify the attribute to use.
services:
_defaults:
autowire: true
HelloFresh\MenuService\Presentation\HTTP\Action\App\HelloAction:
tags:
- { name: action, key: action.app.hello }
HelloFresh\MenuService\Presentation\HTTP\Middleware\ActionMiddleware:
arguments:
$actionLocator: !tagged_locator { tag: action, index_by: key }
Specify the index key with a static method
Use default_index_method
to specify the name of the static method to use on your service's class.
I wouldn't use that option, because it leaks DIC concerns into the application, but the option is there if you need it.
services:
_defaults:
autowire: true
HelloFresh\MenuService\Presentation\HTTP\Action\App\HelloAction:
tags:
- { name: action }
HelloFresh\MenuService\Presentation\HTTP\Middleware\ActionMiddleware:
arguments:
$actionLocator: !tagged_locator { tag: action, default_index_method: getLocatorIndexKey }
<?php
namespace HelloFresh\MenuService\Presentation\HTTP\Action\App;
class HelloAction {
static public function getLocatorIndexKey() {
return 'action.app.hello';
}
// ...
}
tl;dr
Save yourself some time, and therefore money, by using the keyword !tagged_locator
instead of
maintaining your own dependency injection container compiler pass and its companion service locator.