Problem/Motivation

In Drupal 11, hook_module_implements_alter no longer guarantees hook execution order due to the new hook system (HookCollectorPass). The entity_form_steps module relies on its hook_form_alter running after field_group's hook_form_alter, ensured via entity_form_steps_module_implements_alter(). When the ordering fails, $form['#fieldgroups'] is not populated when EntityFormSteps::alterForm() executes.

This causes a cascade of failures:

  1. setAccess() cannot hide fields on inactive steps because the isset($form['#fieldgroups'][$stepName]) check fails, so fields from all steps remain visible.
  2. setValidation() cannot populate #limit_validation_errors for the current step's fields.
  3. #limit_validation_errors remains as [] (empty array), which causes Drupal's FormBuilder::handleInputElement() to skip all input processing.
  4. The entity is saved with NULL values for fields that were on previous steps, causing database integrity constraint violations (e.g., Column 'title' cannot be null).

The user sees: "The content could not be saved. Contact the site administrator if the problem persists."

Steps to reproduce

  1. Install Drupal 11.3.x with entity_form_steps 1.1.7 and field_group.
  2. Create a content type with a text field (e.g., field_notes).
  3. Configure the form display with two steps:
    • Step 1: title field, with a "Continue" button.
    • Step 2: field_notes field, with a "Save" button.
  4. Go to node/add/{content_type}.
  5. Fill in the title on step 1 and click "Continue".
  6. Fill in the notes on step 2 and click "Save".
  7. Error: "The content could not be saved". The title is NULL because the form values from step 1 were not processed.

Root cause: entity_form_steps_module_implements_alter() moves the module's form_alter to the end of the implementations list. In Drupal 11's new hook system, this ordering mechanism is no longer reliable. When entity_form_steps_form_alter runs before field_group_form_alter, $form['#fieldgroups'] does not exist yet.

Proposed resolution

Add a fallback in EntityFormSteps::alterForm() that populates $form['#fieldgroups'] by calling field_group_attach_groups() when field groups have not been attached yet. This is a defensive check that works regardless of hook execution order:

if (!isset($form['#fieldgroups']) && function_exists('field_group_attach_groups')) {
  $storage = $form_state->getStorage();
  if (!empty($storage['form_display'])) {
    field_group_attach_groups($form, [
      'entity_type' => $entity->getEntityTypeId(),
      'bundle' => $entity->bundle(),
      'entity' => $entity,
      'context' => 'form',
      'display_context' => 'form',
      'mode' => $storage['form_display']->getMode(),
    ]);
  }
}

The patch also includes a test module and 4 functional tests covering: field hiding between steps, saving values across steps, editing, and back navigation.

Remaining tasks

  • Review the proposed fix.
  • Consider migrating from hook_module_implements_alter to Drupal 11's #[Hook] attribute with order parameter as a long-term solution.

User interface changes

None.

API changes

None.

Data model changes

None.

Comments

facine created an issue.