Misleading documentation in \Drupal\Core\Entity\Annotation\EntityReferenceSelection

Created on 22 March 2022, over 2 years ago
Updated 13 June 2024, 5 months ago

Problem/Motivation

In #2945789: Document quirks of Entity reference selection plugins while a fix is figured out โ†’ , documentation was added to core/lib/Drupal/Core/Entity/Annotation/EntityReferenceSelection.php that indicates:

There are some implementation bugs that make the plugin available only if the ID follows a specific pattern. It must be either identical to group or prefixed with the group. E.g. if the group is "foo" the ID must be either "foo" or "foo:bar".

As far as I can tell, even this is incorrect. As hinted at by ๐Ÿ› Entity reference selection plugins break when not following a weird ID pattern Needs work , it appears that "id" and "group" must be identical almost 100% of the time. The two ALWAYS have to be identical when using a "deriver" such as "Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver". If no deriver is used and the "id" is prefixed with the "group", the second part has to be the entity type ID, but then Drupal will expect to find a "base plugin" of some kind. This will result in the following PHP notice:

Notice: Undefined index: base_plugin_label in Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem->fieldSettingsForm() (line 404 of core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php).
Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem->fieldSettingsForm(Array, Object) (Line: 117)
Drupal\field_ui\Form\FieldConfigEditForm->form(Array, Object) (Line: 106)
Drupal\Core\Entity\EntityForm->buildForm(Array, Object)
call_user_func_array(Array, Array) (Line: 532)
Drupal\Core\Form\FormBuilder->retrieveForm('field_config_edit_form', Object) (Line: 278)

Steps to reproduce

Define an ER plug-in like this in an enabled module called my_module:


namespace Drupal\my_module\Plugin\EntityReferenceSelection;

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

/**
 * Sample entity reference selection #1.
 * 
 *
 * @EntityReferenceSelection(
 *   id = "sample:taxonomy_term",
 *   label = @Translation("Sample Taxonomy Term selection"),
 *   group = "sample",
 *   weight = 0,
 *   entity_types = {"taxonomy_term"}
 * )
 */
class SampleSelection1 extends DefaultSelection {
}

This plug-in will display a PHP notice about the missing base plugin info.

Now, define a second ER plug-in like this, in the same module:


namespace Drupal\my_module\Plugin\EntityReferenceSelection;

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

/**
 * Sample entity reference selection #2.
 * 
 *
 * @EntityReferenceSelection(
 *   id = "sample:taxonomy_term",
 *   label = @Translation("Sample Taxonomy Term selection"),
 *   group = "sample",
 *   weight = 0,
 *   deriver = "Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver",
 * )
 */
class SampleSelection2 extends DefaultSelection {
}

This plugin won't show up at all.

Proposed resolution

Ideally, ๐Ÿ› Entity reference selection plugins break when not following a weird ID pattern Needs work would fix the root cause of why "id" and "group" are so intertwined and this issue is unnecessary.

Alternatively:

  1. Eliminate "group" as it seems rather pointless to allow devs to define it in the first place; OR
  2. Fix the docs to indicate that devs want to set these two annotation fields to the same value 100% of the time.

Remaining tasks

Update the docs.

User interface changes

API changes

Data model changes

Release notes snippet

๐Ÿ› Bug report
Status

Active

Version

11.0 ๐Ÿ”ฅ

Component
Entityย  โ†’

Last updated 1 day ago

  • Maintained by
  • ๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom @catch
  • ๐Ÿ‡จ๐Ÿ‡ญSwitzerland @berdir
  • ๐Ÿ‡ฉ๐Ÿ‡ชGermany @hchonov
Created by

๐Ÿ‡บ๐Ÿ‡ธUnited States GuyPaddock

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • ๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom joachim

    > would fix the root cause of why "id" and "group" are so intertwined and this issue is unnecessary.

    Having looked at this code a bit more, I'm starting to understand why id and group behave like this.

    And YES, it does need to be documented MUCH better.

    > The two ALWAYS have to be identical when using a "deriver" such as "Drupal\Core\Entity\Plugin\Derivative\DefaultSelectionDeriver". If no deriver is used and the "id" is prefixed with the "group", the second part has to be the entity type ID, but then Drupal will expect to find a "base plugin" of some kind.

    That's mostly right, but not entirely. When a plugin declares a deriver, then the base plugin ID is not registered as a plugin ID. So, core has:

    #[EntityReferenceSelection(
      id: "default",
      group: "default",
      deriver: DefaultSelectionDeriver::class,
    )]
    

    which means there is no plugin with ID just 'default'.

    What's going on with the 'Notice: Undefined index: base_plugin_label' error is that EntityReferenceItem is expecting that a plugin with an ID of the form 'GROUP:ENTITY_TYPE' is made with a deriver, and the deriver does this:

    $this->derivatives[$entity_type_id]['base_plugin_label'] = (string) $base_plugin_definition['label'];
    

    So that's something else to fix -- if base_plugin_label isn't set, it should fall back to the plugin label.

    Part of the complication here is that some of the assumptions about how id & group work are in the field system, in EntityReferenceItem. That's a code smell, because that should be a mere consumer. Technically, as the code stands, another consumer of these plugins could name a plugin 'cats:dogs' and specify that as '#selection_handler' in an entity_autocomplete form element. Except as you point out, an entityreference field would break.

    So as well as documenting, we should open an issue to have the SelectionPluginManager's discovery process throw exceptions for plugin IDs that don't conform to the expected pattern, so that developers see the problem earlier.

    The reason for all this is that basically, the plugin ID is NOT REALLY THE ID. Sort of. The thing the user actually is selecting when they configure an entityreference field is the group. The rest of the plugin ID is to specialise by entity type.

    The expectation is that you either have:

    - group and id match. There is only one plugin for all entity types (which it declares it can handle)
    - group 'foo' and ids 'foo:ENTITY_TYPE'. AFAICT there is no point declaring a different foo-group plugin for an entity type, such as 'foo:node_extra', because it won't show in the field settings, even if you declare the 'entity_types' property. In fact, AFAICT the entity_types property is redundant because of this, and we could consider removing it.

    So in core we have the 'default' group, which uses a base class DefaultSelection, with a deriver which produces plugins 'default:ENTITY_TYPE'. Core then makes use of how you can override the class for a derived plugin, by having classes like NodeSelection with specialised handling for nodes.

    If you wanted to provide an alternative way to handle nodes, you need to define a new *group*, and the plugin for nodes in that group, like this:

     * @EntityReferenceSelection(
     *   id = "test_3270898:node",
     *   label = @Translation("Node test_3270898"),
     *   base_plugin_label = @Translation("Node test_3270898"),
     *   group = "test_3270898",
     *   entity_types = {
     *     "node",
     *   },
     *   weight = 0,
     * )
    

    Then, to complicate matters, there is automatic selection in SelectionPluginManager to do with the weight. If you call getInstance() with a plugin ID with no ':' in it it tries to find the 'best' plugin ID within that group which can handle the entity type. This is assuming that in the same group you might have, say:

    - mygroup:node
    - mygroup:node_extra
    - mygroup:node_amazing

    and that it should pick the one with the highest weight. This seems all rather pointless, since EntityReferenceItem will have saved the field configuration with 'mygroup:node' as the handler ID.

  • ๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom joachim

    > So that's something else to fix -- if base_plugin_label isn't set, it should fall back to the plugin label.

    I'm actually wondering whether that should be fixed.

    If you want to create a plugin that applies to just one entity type, use a singleton with the same plugin ID and group name.

    If you want to create plugins for several entity types, either create several singletons, or use a deriver so they apply to all entity types.

  • Dr.Paul โ†’ made their first commit to this issueโ€™s fork.

  • ๐Ÿ‡ง๐Ÿ‡ชBelgium kristiaanvandeneynde Antwerp, Belgium

    From what I can recall (from 8 years ago, mind you), the whole group thing is so you could specify an ER field where you don't care about the selection plugin, as long as it's tailored towards e.g. nodes. Then it would default to whatever core has, but if someone else came up with a "better" selection plugin, that one would be used instead.

    In practice, I have yet to see anyone make use of that because often the one creating the selection plugin is the one consuming it. E.g.: I have a really specific group role selection plugin in Group that I do not expect anyone to ever "improve".

  • ๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom joachim

    The way I understand it is that the 'group' is SECRETLY the plugin ID. It's what you select in the UI.

    So OOTB you get to pick between 'default' and 'views'.

    But instead of making those the plugin IDs, they are the group instead, so that we can use the plugin derivative system to get specialised plugin classes for different entity types.

    Which is a hack, really.

    Conceptually, what we really want here are dynamically-selected plugin classes. But that would require a whole new concept and an additional way of declaring which plugin class to use for what condition.

  • ๐Ÿ‡ฎ๐Ÿ‡ณIndia heykarthikwithu Bengaluru ๐ŸŒ

    heykarthikwithu โ†’ changed the visibility of the branch 3270898-misleading-documentation-in to hidden.

Production build 0.71.5 2024