After debugging and struggling for hours trying to implement an entity reference selection plugin (ERSP), I've come to the following conclusion: An ERSP must declare its ID in the form of <group> or <group>:<id> or it will not work whatsoever.
Assume an annotation like this:
/**
* Only shows the group roles which are available for a group type.
*
* @EntityReferenceSelection(
* id = "group_type_roles",
* label = @Translation("Group type role selection"),
* entity_types = {"group_role"},
* group = "group_type",
* weight = 0
* )
*/
class GroupTypeRoleSelection extends DefaultSelection {
}
This will not work, but either id = "group_type",
or id = "group_type:roles",
will.
Examples of places where it breaks below.
1. When saving FieldConfig entities with a handler set to an "invalid" ERSP ID
Assume a FieldConfig like this:
FieldConfig::create([
// The storage is that of an ER field.
'field_storage' => FieldStorageConfig::loadByName('group_content', 'group_roles'),
'bundle' => 'foo',
'label' => $this->t('Roles'),
'settings' => [
'handler' => 'group_type_role',
'handler_settings' => [],
],
])->save();
If you try to save a field configuration with the ID directly set, your site breaks. This is because when it tries to load the plugin in SelectionPluginManager::getInstance(), it calls SelectionPluginManager::getPluginId() with $target_type "group_role" (correct) and $base_plugin_id "group_type_role" (breaks).
The reason it breaks is because SelectionPluginManager::getPluginId() will then treat the $base_plugin_id as a group name, where it's actually a plugin name. It can therefore not find any plugin ID to return, leading to the plugin not being instantiated and your ER field breaking completely.
2. When saving FieldConfig entities with a handler set to a group
In other places in code, it seems acceptable to just define a group as the handler setting, because it will then fall back to the handler with the highest weight for that group. This could be desirable behavior, as it allows other modules to intervene by defining a plugin with a higher weight.
As proven above, SelectionPluginManager::getPluginId() actually expects a group name instead of a plugin name.
Assume a FieldConfig like this:
FieldConfig::create([
// The storage is that of an ER field.
'field_storage' => FieldStorageConfig::loadByName('group_content', 'group_roles'),
'bundle' => 'foo',
'label' => $this->t('Roles'),
'settings' => [
'handler' => 'group_type',
'handler_settings' => [],
],
])->save();
The code in
#2578249: Some e_r fields get the wrong Selection handler →
breaks this, however, unless your ERSP follows the naming pattern mentioned above.
This code works fine everywhere, up to the point where field_field_config_presave() intervenes and overwrites your handler setting with 'group_type_roles' because of the following lines:
list($current_handler) = explode(':', $field->getSetting('handler'), 2);
$field->setSetting('handler', $selection_manager->getPluginId($target_type, $current_handler));
As stated in the previous section, having a handler set to 'group_type_roles' will break your site.
3. When trying to configure an ER field to use your plugin through the UI
In EntityReferenceItem::fieldSettingsForm(), there is code that completely ignores your ERSP unless it follows the aforementioned plugin ID pattern:
// Get all selection plugins for this entity type.
$selection_plugins = \Drupal::service('plugin.manager.entity_reference_selection')->getSelectionGroups($this->getSetting('target_type'));
$handlers_options = array();
foreach (array_keys($selection_plugins) as $selection_group_id) {
// We only display base plugins (e.g. 'default', 'views', ...) and not
// entity type specific plugins (e.g. 'default:node', 'default:user',
// ...).
if (array_key_exists($selection_group_id, $selection_plugins[$selection_group_id])) {
$handlers_options[$selection_group_id] = Html::escape($selection_plugins[$selection_group_id][$selection_group_id]['label']);
}
elseif (array_key_exists($selection_group_id . ':' . $this->getSetting('target_type'), $selection_plugins[$selection_group_id])) {
$selection_group_plugin = $selection_group_id . ':' . $this->getSetting('target_type');
$handlers_options[$selection_group_plugin] = Html::escape($selection_plugins[$selection_group_id][$selection_group_plugin]['base_plugin_label']);
}
}
This would only list my plugin if it had an ID of 'group_type' or 'group_type:group_role'. Do note that my plugin is under $selection_plugins['group_type']['group_type_role']
and can thus not be found by the above code.
All of the above leads to the conclusion that your ERSP must declare its ID as mentioned at the top of this issue summary or it will just not work.