Form & Render Element default properties not properly merged when property is an array of callbacks

Created on 20 August 2013, over 11 years ago
Updated 10 May 2023, over 1 year ago

Problem

RenderElements allow you to define "default" element properties through the getInfo() method. Eventually when the element is rendered, each default propertiy is added to the render array only if it doesn't already exist.

However, this means that any of the callback properties (i.e. #process, #after_build, #validate, #element_validate, #pre_render, #post_render, #submit) defaults will be overwritten if a render array contains its own custom callback. Resulting in a loss of "default" functionality.

Take, for example, the entity_autocomplete render element from core. It has multiple #process and #pre_render callbacks defined in it's getInfo() definition. It looks something like

[   
  // ... etc
  '#process' => [
    Drupal\Core\Entity\Element\EntityAutocomplete::processEntityAutocomplete,
    Drupal\Core\Entity\Element\EntityAutocomplete::processAutocomplete,
    Drupal\Core\Entity\Element\EntityAutocomplete::processAjaxForm,
    Drupal\Core\Entity\Element\EntityAutocomplete::processPattern,
    Drupal\Core\Entity\Element\EntityAutocomplete::processGroup,
  ],
  '#pre_render' => [
    Drupal\Core\Entity\Element\EntityAutocomplete::preRenderTextfield,
    Drupal\Core\Entity\Element\EntityAutocomplete::preRenderGroup,
  ],
  // ... etc
];

However if you create a render array of this type with your own #process and #pre_render hooks, they will overwrite the original defaults and your autocomplete widget won't work:


$element = [
  '#type' => 'entity_autocomplete',
  '#title' => 'Lookup Entities!',
  '#target_type' => 'node',
  '#selection_handler' => 'default:node',
  '#selection_settings' => [
    'target_bundles' => [
      'page' => 'page',
    ],
    'sort' => [
      'field' => '_none',
    ],
    'auto_create' => FALSE,
    'auto_create_bundle' => '',
    'match_operator' => 'CONTAINS',
  ],
  // Remove these last two properties and the element will work
  '#process' => [
    // ... my custom process callbacks
  ],
  '#pre_render' => [
    // ... my custom process callbacks
  ],
];

Proposed resolution

Use Drupal\Component\Utility\NestedArray::mergeDeep() to merge defaults instead. This will ensure that:

  • any scalar values will be only be used when they don't exist on the array being rendered
  • callback arrays will be merged, with the defaults running first, followed by any custom ones in the render array

Remaining tasks

Testing, Tests, Approval

API changes

This may be seen as an API change, but IMO if there is code in core or contrib that uses this "feature" to disable or overwrite default behavior, that is to be considered hacking and abusing.

πŸ› Bug report
Status

Closed: duplicate

Version

9.5

Component
RenderΒ  β†’

Last updated about 8 hours ago

Created by

πŸ‡«πŸ‡·France fietserwin Le Mont-Dore

Live updates comments and jobs are added and updated live.
  • Needs tests

    The change is currently missing an automated test that fails when run with the original code, and succeeds when the bug has been fixed.

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.

Production build 0.71.5 2024