Change record status: 
Project: 
Introduced in branch: 
2.0.x
Introduced in version: 
2.0.0
Description: 

Quick recap:

In order to allow any module to have a say in the output of a GroupRelationType handler's output, we needed to move from annotation-declared handlers to service-declared handlers. More detail in the issue and this change record: https://www.drupal.org/node/3222292

Service structure:

This means that, for each handler, we now have two services: The default one and the plugin-specific one:

# group.services.yml
group.relation_handler.SOME_HANDLER_MACHINE_NAME:
    class: 'Drupal\group\Plugin\Group\RelationHandlerDefault\SomeHandler'
    arguments: ['@some.dependency']
    shared: false

# mymodule.services.yml
group.relation_handler.SOME_HANDLER_MACHINE_NAME.MY_PLUGIN_BASE_ID:
    class: 'Drupal\mymodule\Plugin\Group\RelationHandlerDefault\MyPluginSomeHandler'
    arguments: ['@group.relation_handler.SOME_HANDLER_MACHINE_NAME']
    shared: false

Please pay close attention to the naming pattern of the services and the fact that the plugin-specific one is required to accept the default service as its dependency.

The decorator chain:

Because of this structure, we can now add customizations to either the defaults (i.e. to ALL plugins) or to a single specific plugin by using Symfony's decorator pattern. So, in essence, default service A can be decorated by B and C and then when the specific service Z is invoked, it gets (C > B > A) injected rather than simply A. But because Z is a service of its own, it can also be decorated by X and Y, leading to the following chain: X > Y > Z > C > B > A. Pretty cool, huh?

# mymodule.services.yml
  # Adds foo admin permission to all plugins.
  group.relation_handler_decorator.permission_provider.foo:
    class: 'Drupal\mymodule\Plugin\Group\RelationHandler\FooAdminPermissionProvider'
    decorates: 'group.relation_handler.permission_provider'
    decoration_priority: 100
    arguments: ['@group.relation_handler_decorator.permission_provider.foo.inner']
    shared: false

  # Adds baz admin permission to the group_membership plugin.
  group.relation_handler_decorator.permission_provider.baz.group_membership:
    class: 'Drupal\mymodule\Plugin\Group\RelationHandler\BazAdminPermissionProvider'
    decorates: 'group.relation_handler.permission_provider.group_membership'
    decoration_priority: 300
    arguments: ['@group.relation_handler_decorator.permission_provider.baz.group_membership.inner']
    shared: false

A few notes:

  • The naming pattern is for you to choose, I chose to use relation_handler_decorator to be specific, but the entire service name does not matter.
  • What you put in decorates is what matters. That tells Symfony whether you're decorating the default or specific service.
  • Decoration priority is to decide which one comes first. The higher the number, the sooner your decorator gets called. These priorities are taken into account separately for each service you're decorating! So regardless of priority, all default decorators run first (in order), then the specific decorators (in order).
  • The parent in the chain is passed in as '@MY_SERVICE_NAME.inner'. In later Symfony version we will be able to just write '@.inner'
  • All decorators must be defined as non-shared

For more detail, see: \Drupal\Tests\group\Kernel\RelationHandlerTest
A very detailed video about this can be found on the Factorial YouTube channel: https://www.youtube.com/watch?v=7Ugu0O_UEbE

Impacts: 
Site builders, administrators, editors
Module developers
Site templates, recipes and distribution developers