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.

Note: 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.