13

This is a question about the Link module. Since with the Link module you can enter external or internal links both, we strongly rely on it.

Unfortunately the number of items to be displayed from its autocomplete field is limited to 10. We have a lot of nodes with nearly identical titles and so it comes that the node we are searching for doesn't get displayed in the autocomplete field when there are more than 10 matching titles.

The limit is hardcoded in core/lib/Drupal/Core/Entity/EntityAutocompleteMatcher.php. Is there an elegant way to increase this little number from within a custom module? Do I have to extend class EntityAutocompleteMatcher? Where would I have to place my extension and how to ensure it gets executed from within the link widget?

6 Answers 6

9
+50

I suppose overriding EntityAutocompleteMatcher will affect all autocomplete form elements on your site. So that I would create a new entity selection plugin instead because it is more granular approach. The plugin could be enabled per field. Here is an example of such a plugin. https://drupal.stackexchange.com/a/220136/433

In your case the implementation would be even more trivial:

File: modules/example/src/Plugin/EntityReferenceSelection/ExampleSelection.php

namespace Drupal\example\Plugin\EntityReferenceSelection;

use Drupal\node\Plugin\EntityReferenceSelection\NodeSelection;

/**
 * Entity reference selection.
 *
 * @EntityReferenceSelection(
 *   id = "example:node",
 *   label = @Translation("Example node"),
 *   group = "example",
 * )
 */
class ExampleSelection extends NodeSelection {

  /**
   * {@inheritdoc}
   */
  public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
   return parent::getReferenceableEntities($match, $match_operator, 25);
  }

}

Using NodeSelection as base class instead of DefaultSelection would allow you to filter referenced nodes by their status. Note that referencing other entity types is not supported yet.

Unlike entity reference link widget does not allow to specify selection plugin through user interface, therefore you need to set it programmatically using hook_field_widget_WIDGET_TYPE_form_alter().

/**
 * Implements hook_field_widget_WIDGET_TYPE_form_alter().
 */
function example_field_widget_link_default_form_alter(&$element, \Drupal\Core\Form\FormStateInterface $form_state, $context) {
  // Replace default selection handler to increase limit of displayed entities.
  $element['uri']['#selection_handler'] = 'example:node';
}

It is important that the plugin ID contains a semicolon.

0
10

If you can live with overriding all of the autocomplete limits, you can override a core service in Drupal 8;

The service you need to override is here in core.services.yml:

  entity.autocomplete_matcher:
    class: Drupal\Core\Entity\EntityAutocompleteMatcher
    arguments: ['@plugin.manager.entity_reference_selection']

In your custom module, add a class that implements ServiceModifierInterface

namespace Drupal\mymodule;

use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceModifierInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

class MyModuleServiceProvider implements ServiceModifierInterface {

  /**
   * Modifies existing service definitions.
   *
   * @param ContainerBuilder $container
   *   The ContainerBuilder whose service definitions can be altered.
   */
  public function alter(ContainerBuilder $container) {

    for ($id = 'entity.autocomplete_matcher'; $container->hasAlias($id); $id = (string) $container->getAlias($id));
    $definition = $container->getDefinition($id);
    $definition->setClass('Drupal\mymodule\Entity\EntityAutocompleteMatcherCustom');
    $container->setDefinition($id, $definition);
  }

}

Then copy EntityAutocompleteMatcher.php into your module at /src/Entity/EntityAutocompleteMatcherCustom.php

Then update the hardcoded 10 to a 50, or whatever limit you'd like:

namespace Drupal\mymodule\Entity;

use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Tags;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface;
use Drupal\Core\Entity\EntityAutocompleteMatcher;

/**
 * Matcher class to get autocompletion results for entity reference.
 */
class EntityAutocompleteMatcherCustom extends EntityAutocompleteMatcher {

  /*
   * {@inheritdoc]
   */
  public function getMatches($target_type, $selection_handler, $selection_settings, $string = '') {

    $matches = array();

    $options = array(
      'target_type' => $target_type,
      'handler' => $selection_handler,
      'handler_settings' => $selection_settings,
    );
    $handler = $this->selectionManager->getInstance($options);

    if (isset($string)) {
      // Get an array of matching entities.
      $match_operator = !empty($selection_settings['match_operator']) ? $selection_settings['match_operator'] : 'CONTAINS';
      // Changing limit from 10 to 50.
      $entity_labels = $handler->getReferenceableEntities($string, $match_operator, 50);

      // Loop through the entities and convert them into autocomplete output.
      foreach ($entity_labels as $values) {
        foreach ($values as $entity_id => $label) {
          $key = "$label ($entity_id)";
          // Strip things like starting/trailing white spaces, line breaks and
          // tags.
          $key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(Html::decodeEntities(strip_tags($key)))));
          // Names containing commas or quotes must be wrapped in quotes.
          $key = Tags::encode($key);
          $matches[] = array('value' => $key, 'label' => $label);
        }
      }
    }

    return $matches;
  }

}

Obviously override core services has some risks, but it's cool that you can do this.

What are the risks of overriding a core service?

1) You may lose the benefits of updates when you update core. If there is a critical security fix in the service, and your altered copy has the security hole, you will not benefit from the community updating that code.

2) Other modules you install may have dependencies to the original service with its original feature set. So let's say there is some code in another module that will break if the number of autocomplete entries are greater than or less than 10, you won't know about it, until it affects you.

3) It makes your codebase harder to maintain. You have to remember that you are not using core Drupal, but an extended version. Other developers who join your project after you left may have a hard time figuring out why a service is behaving in a non-standard way.

Is this hacking core?

Depends on how you look at it. It's not going into the core module and changing code. It's not even creating a patch and applying it and tracking it with a package manager such as composer. It's more of a one-off customization that alters a sites core behavior, similiar to an ALTER hook. It's more self-contained that a core hack, because it's within your own custom module on your site. So core updates to the original service will not be affected, the same way as if you patched or hacked the original service code.

But it does have some of the same risks as hacking core, as mentioned above.

In the original question, the issue was the node titles are not unique enough. The better solution, other than changing the limit globally on drop downs would be solve the uniqueness problem.

What I would suggest is to add a new field field_display_title and use that on the page, and if you need it another field field_teaser_title for display on list pages where you need a shorter title. Then the actual title that gets pulled into entity reference select dropdown can be useful to your editors and be unique, such as "My Article (page 1)" if the issue is each page has the same title. Then you don't have to override a core service.

When you come across a problem with Drupal, try to find the solution that requires the least amount of custom code. This makes your site more stable, easier to maintain and saves you time.

3
  • 3
    Basically overriding a core service has the same implications as implementing ALTER hooks. The risks take place but they are quite minor and can be mitigated with proper code documentation.
    – ya.teck
    Nov 19, 2016 at 10:13
  • 1
    Of cause a good number of such overrides, hooks, patches can reduce project maintainability.
    – ya.teck
    Nov 19, 2016 at 10:22
  • This seems to me like a perfect use case for a [service decorator] (blueoakinteractive.com/blog/service-decorators-drupal-8).
    – Beau
    Apr 8, 2019 at 23:15
5

An other easy way to modify the number of results is to alter the range value in the query:

/**
 * Implements hook_query_TAG_alter() for entity reference selection handlers.
 *
 * We like tho show always 30 results instead of the 10 definied in EntityAutocompleteMatcher::getMatches()
 */
function MODULE_query_entity_reference_alter(AlterableInterface $query) {
  $query->range(0, 30);
}
5

It looks like a proper fix for this has been added to the latest version of core, 8.8+, see #2863188 Hardcoded result size limit in the entity reference autocomplete widget.

enter image description here

1
  • Unfortunately this is only works for the entity reference widget, not for the link widget.
    – leymannx
    Feb 14 at 9:55
3

@Weri, I would avoid doing that, having just implemeted your suggestion and spent the best part of a day trying to fix another issue.

The query alter you suggest also affects entiry reference when linking paragraphs to nodes. A node I was woking on had 80+ paragraph item before I added the alter. Once added I was unable to save the node. Removing/Commenting out the alter fixed the issue.

Update

Wrapping the $query->range() in a route check fixes the issue for me, example,

function mymodule_query_entity_reference_alter($query) {
  $routeMatch = \Drupal::routeMatch();
  if ($routeMatch->getRouteName() == 'system.entity_autocomplete') {
    $query->range(0, 20);
  }
}
1

There is an issue on Drupal.org to extend link widget to have limit and operator settings the same way as for entity reference autocomplete widget https://www.drupal.org/project/drupal/issues/3341689#comment-15141098

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.