Documentation

Tagging

Tagging

Label services by capability so they can be discovered and processed in groups.

Services configured in your container can also be tagged. In the service container, a tag implies that the service is meant to be used for a specific purpose.

import UserRepository from './Entity/UserRepository'
import {ContainerBuilder, Definition} from 'node-dependency-injection'

let container = new ContainerBuilder()
let definition = new Definition(UserRepository)
definition.addTag('repository')
container.setDefinition('app.entity.user_repository', definition)

Tags, then, are a way to tell to your app that your service should be registered or used in some special way by the app.

Tags on their own don't actually alter the functionality of your services in any way. But if you choose to, you can ask a container builder for a list of all services that were tagged with some specific tag. This is useful in compiler passes where you can find these services and use or modify them in some specific way.

For example:

// ./Entity/UserRepository
class UserRepository {
  constructor (someManager) {
    this._someManager = someManager
  }
  
  // ...
}

// ./Entity/AccountRepository
class AccountRepository {
  constructor (someManager) {
    this._someManager = someManager
  }
  
  // ...
}

So instead of injecting someManager for every repository we can use tags for this purpose.

services:
    app.service.some_service:
        class: ./Service/SomeService

    app.entity.user_repository:
        class: ./Entity/UserRepository
        tags:
            - { name: repository }

    app.entity.account_repository:
        class: ./Entity/AccountRepository
        tags:
            - { name: repository }

You can now use a compiler pass to ask the container for any services with the repository tag:

import {Reference} from 'node-dependency-injection'

class RepositoryPass {
    /**
     * @param {ContainerBuilder} container
     */
    async process (container) {
       let taggedServices = container.findTaggedServiceIds('repository')
       
       for (let [id, definition] of taggedServices) {
         definition.addArgument(new Reference('app.service.some_service'))
       }
    }
}

Adding Additional Attributes on Tags

Sometimes you need additional information about each service that's tagged with your tag. For example, you might want to add an event to each listener.

To begin with, change the services configuration file:

YAML
services:
    app.listener.user_listener:
        class: ./Listener/UserListener
        tags:
              - {name: listener, attributes: {event: postUpdate}}
    
    app.listener.account_listener:
        class: ./Listener/AccountListener
        tags:
              - {name: listener, attributes: {event: prePersist}}
JS
let definition = new Definition(SomeObject)
let attributes = new Map()
attributes.set('event', 'prePersist')
definition.addTag('listener', attributes)
container.setDefinition('app.listener', definition)

Notice that you've added a generic event key to the tag. To actually use this, update the compiler:

class ListenerPass {
    /**
     * @param {ContainerBuilder} container
     */
    async process (container) {
       let taggedServices = container.findTaggedServiceIds('listener')
       
       for (let [id, definition] of taggedServices) {
         for (let tag of definition.tags) {
           definition.addMethodCall(tag.attributes.get('event'))
         }
       }
    }
}

The double loop may be confusing. This is because a service can have more than one tag. You tag a service twice or more with the listener tag. The second for loop iterates over the listener tags set for the current service and gives you one tag object.

Defining service's arguments using tags

You can define the arguments of the services using tags. This is very useful for situations like initializing server components, such as repositories, controllers, etc.

YAML
services:
  repository-manager:
    class: ./../RepositoryManager
    arguments: ["!tagged repository"]

  repository-foo:
    class: ./../RepositoryFoo
    tags:
      - { name: repository }
  
  repository-bar:
    class: ./../RepositoryBar
    tags:
      - { name: repository }
JS
import {ContainerBuilder, Definition, TagReference} from 'node-dependency-injection'
import RepositoryManager from './../RepositoryManager'
import RepositoryFoo from './../RepositoryFoo'
import RepositoryBar from './../RepositoryBar'

let container = new ContainerBuilder()

container
  .register('repository_manager', RepositoryManager)
  .addArgument(new TagReference('repository'))

container
  .register('repository_foo', RepositoryFoo)
  .addTag('repository')

container
  .register('repository_bar', RepositoryBar)
  .addTag('repository')

TagReference class

TagReference is a special reference that, when used as a constructor argument, tells the container to inject all services tagged with a given name as an array.

This is the programmatic equivalent of the "!tagged <tagName>" YAML syntax.

import {TagReference} from 'node-dependency-injection'

// Inject all services tagged 'repository' into the first argument
definition.addArgument(new TagReference('repository'))

The container will resolve the TagReference at compile time and pass an array of all matching service instances to the constructor.