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.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

kristiaanvandeneynde created an issue. See original summary.

amateescu’s picture

Status: Active » Needs review
FileSize
2.75 KB

Since #2578249: Some e_r fields get the wrong Selection handler, we try to store only selection plugin IDs in the 'handler' setting of entity reference fields, so the real problem is SelectionPluginManager::getInstance() which shouldn't try to "resolve" it again.

And you're right, the second parameter expected by SelectionPluginManager::getPluginId() is actually a selection group.

Let's try and see how much this breaks :)

Status: Needs review » Needs work

The last submitted patch, 2: 2649712.patch, failed testing.

kristiaanvandeneynde’s picture

So what you're saying is that this is indeed leftover code from what the system used to do? Great, then it can be removed. It would seem that the logic in field_field_config_presave() also needs taking care of. And I can imagine some tests specifying a group instead of a plugin ID in their test setups, which would cause a few test fails left and right.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

jibran’s picture

Component: entity_reference.module » entity system
Issue tags: +DER issue

Moving to right component

cilefen’s picture

Issue summary: View changes

I changed the acronym so I can understand it.

kristiaanvandeneynde’s picture

Good catch, thanks.

tacituseu’s picture

Judging by the code, for most custom cases id should be equal to group (e.g. core\modules\views\src\Plugin\EntityReferenceSelection\ViewsSelection.php)

The other case seems to be a way of minimizing boilerplate for DefaultSelection plugin (see core\lib\Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection.php) and expects the definition to be derived by core\lib\Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver.php otherwise it won't have required 'base_plugin_label' key.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

larowlan’s picture

This impacts on Workbench Access too

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

joachim’s picture

> An ERSP must declare its ID in the form of : or it will not work whatsoever.

I don't think that's the only possible format.

I've got an ERS plugin working with the plugin & the group as the same value, e.g.

 * @EntityReferenceSelection(
 *   id = "my_plugin",
 *   label = @Translation("My plugin label."),
 *   group = "my_plugin",
 *   entity_types = {"node"},
 *   weight = 10,

The crucial code is in fieldSettingsForm():

    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 is basically making a whole bunch of assumptions about core's ERS plugins, which use derivatives, which doesn't make sense for contrib and custom plugins, which will often be one-offs.

To me, that's the job of the plugin manager to give you the right list of plugins. The form element should not have to do this filtering work.

getPluginId() is really weird. I don't understand at all why we need to be doing this:

  /**
   * Gets the plugin ID for a given target entity type and base plugin ID.
   */
  public function getPluginId($target_type, $base_plugin_id);

and why it sorts on weight, and then takes the last item:


    // Sort the selection plugins by weight and select the best match.
    uasort($selection_handler_groups[$base_plugin_id], ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']);
    end($selection_handler_groups[$base_plugin_id]);
    $plugin_id = key($selection_handler_groups[$base_plugin_id]);

If the 'weight' plugin property is a means to determine which one is picked, then it is REALLY badly named. I had assumed it was simply to order the plugin labels in the UI!

As for the group property, I don't understand it at all:

  /**
   * The selection plugin group.
   *
   * This property is used to allow selection plugins to target a specific
   * entity type while also inheriting the code of an existing selection plugin.
   * For example, if we want to override the NodeSelection from the 'default'
   * selection type, we can define the annotation of a new plugin as follows:

If I want to inherit the code of an existing selection plugin... I could just inherit from that class, no?

pwolanin’s picture

Just spent a while wondering why my selection plugin didn't work until I realized from the plugin manager code I needed to make the plugin match the name of the group.

The issue summary is wrong as #15 says, but it's certainly not documented clearly.

joachim’s picture

> Just spent a while wondering why my selection plugin didn't work until I realized from the plugin manager code I needed to make the plugin match the name of the group.

This issue is going to take time to fix. In the meantime, let's stop wasting developer time figuring out how to use these over and over again, and document the quirks: #2945789: Document quirks of Entity reference selection plugins while a fix is figured out.

kristiaanvandeneynde’s picture

@joachim in #15 and #17: Completely agree, nice write-up.
@pwolanin in #16: The IS isn't necessarily wrong as the way I fixed it in Group is by doing what the IS explains: Following the undocumented pattern.

 * @EntityReferenceSelection(
 *   id = "group_type:group_role",
 *   label = @Translation("Group type role selection"),
 *   entity_types = {"group_role"},
 *   group = "group_type",
 *   weight = 0
 * )

That said, now that I know more about D8 and selection plugins, it does seem like core only expects these to be derivatives of the "base plugin" representing the group. Which is why core has some overrides for certain derivatives. (As mentioned in #10)

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

joachim’s picture

Title: Entity reference selection plugins break when not following an undocumented ID pattern » Entity reference selection plugins break when not following a weird ID pattern

Now that #2945789: Document quirks of Entity reference selection plugins while a fix is figured out is in, the pattern is documented. But it's really weird.

benjifisher’s picture

Issue summary: View changes

I ran into this problem recently. I noticed that Drupal\views\Plugin\EntityReferenceSelection\ViewsSelection used the same id and group, so I copied that pattern and decided not to worry about it until I came across this issue.

I am updating the issue summary so that the first few lines describe what does work, based on #2945789: Document quirks of Entity reference selection plugins while a fix is figured out.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

hanoii’s picture

Also stumbled upon this issue after debugging for a bit. It seems you need to supply a group that matches the id

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev
GuyPaddock’s picture

I just filed #3270898: Misleading documentation in \Drupal\Core\Entity\Annotation\EntityReferenceSelection because of this wonky issue. The documentation that was added in #2945789: Document quirks of Entity reference selection plugins while a fix is figured out is misleading because the ID can't be prefixed in all the cases that we've tried.

ravi.shankar’s picture

Added reroll of patch #2 on Drupal 9.3.x.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

hoebekewim’s picture

FileSize
1.63 KB

The re-roll was not correct, in unsets a key inside the array and then uses it.
I reworked the patch to fix it differently, the options were combined, causing the array to merge wrong, if we anyhow insert the 'handler' key, we might as well just set it directly.
This allows the code below it to still execute correctly.

Then another issue occurred with the selection_handler being missing. I have to idea why this is not set by default to the value "default", as stated earlier inside EntityAutoComplete.php, but by adding a value when it is missing, it works out great (you might want to reformat this to an empty check if you are using a PHP version lower than 7.4).

Todo:
- Find out why the selection_handler is not set to a value of "default" by default, or where it gets overwritten.

In between time, this patch should help you guys out.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

solideogloria’s picture

Issue tags: -

What I'm confused about is, if I want to create a sub-plugin of UserSelection, for example, how do I specify that in the annotation?

This does work as a plugin, however it won't show in the list in the UI, so you have to hardcode it in the config YAML or PHP.

 * @EntityReferenceSelection(
 *   id = "default:user:custom",
 *   label = @Translation("Custom User"),
 *   entity_types = {"user"},
 *   group = "default",
 *   weight = 5
 * )
 */
class CustomUserSelection extends UserSelection {
Allan.ordogh’s picture

Same problem, my Selection doesn't appear in the Reference method list in the UI with the code :

namespace Drupal\my_module\Plugin\EntityReferenceSelection;

use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
use Drupal\Core\Entity\Query\QueryInterface;

/**
 * Provides entity reference selections to list only the field of the "product" bundle.
 *
 * @EntityReferenceSelection(
 *   id = "custom:my_id",
 *   label = @Translation("My label"),
 *   base_plugin_label = @Translation("My label"),
 *   group = "custom",
 *   weight = 2
 * )
 */
class MyClasseSelection extends DefaultSelection {

   /**
   * {@inheritdoc}
   */
  protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS'): QueryInterface {

    $query = parent::buildEntityQuery($match, $match_operator);
    $query->condition('bundle', 'product');

    return $query;
  }

}

It's interpreted well when i put it in the config file :

settings:
  handler: 'custom:my_id'
  handler_settings:
    target_bundles: null
    sort:
      field: weight
      direction: ASC
    auto_create: false

Context : I want to use an entity reference field on my fields but i can't target the bundle in the UI.

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.