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_decoratorto be specific, but the entire service name does not matter. - What you put in
decoratesis 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