Hello,
i just encounter a very special behavior today concerning the order of execution of form alter hooks.
Explanation :
i wanted to alter a form, already altered in a contrib module by a hook_form_FORM_ID_alter() .
I was unable to make my hook implementation execute after the other implementation, even when i implement the hook hook__module_implements_alter to enforce the execution of my particular hook at the end (the other module did not implement hook__module_implements_alter).
After investigation, i found the problem occurs in the following code inside the method "prepareForm " of core/lib/Drupal/Core/Form/FormBuilder.php
$hooks = ['form'];
if (isset($build_info['base_form_id'])) {
$hooks[] = 'form_' . $build_info['base_form_id'];
}
$hooks[] = 'form_' . $form_id;
$this->moduleHandler->alter($hooks, $form, $form_state, $form_id);
$this->themeManager->alter($hooks, $form, $form_state, $form_id);
the alter method of the ModuleHandler class is called with the three hooks as argument.
inside the method, a merge is done of all form alter hooks (defined by all enabled modules), and (in my install) the order is lost even when enforced by the implementation of hook__module_implements_alter.
Details about the manifestation of the unexpected behavior :
The module editor_advanced_link implements hook_form_FORM_ID_alter()with the function editor_advanced_link_form_editor_link_dialog_alter (altering the form "editor_link_dialog").
to refine the alteration of the form "editor_link_dialog", in a custom module, i also implemented hook_form_editor_advanced_link_form_editor_link_dialog_alter
and hook__module_implements_alter to ensure my hook would be executed after the one defined by the module editor_advanced_link.
Unfortunately order defined by implementing hook__module_implements_alter is lost inside moduleHandler->alter($hooks,...
Quick and dirty solution :
i replaced the code above by the following code, and everything it works (this is a quick and dirty solution).
$hooks = ['form'];
if (isset($build_info['base_form_id'])) {
$hooks[] = 'form_' . $build_info['base_form_id'];
}
// commented code
//$hooks[] = 'form_' . $form_id;
$this->moduleHandler->alter($hooks, $form, $form_state, $form_id);
$this->themeManager->alter($hooks, $form, $form_state, $form_id);
// added code
$this->moduleHandler->alter(['form_' . $form_id], $form, $form_state, $form_id);
$this->themeManager->alter(['form_' . $form_id], $form, $form_state, $form_id);
Indeed, i think hook_form_FORM_ID_alter should be executed after hook_form_alter hooks because they are more specific. There should be a way to enforce this in the code.
i think a more elegant solution would be needed to keep order of form alter hooks and the order of hooks depending on their scope (more or less specific).
maybe something like this or an update of the implementation of method "moduleHandler->alter"
// hook_form_alter
$this->moduleHandler->alter(['form'], $form, $form_state, $form_id);
$this->themeManager->alter(['form'], $form, $form_state, $form_id);
// hook_form_FORM_ID_alter
$this->moduleHandler->alter(['form_' . $build_info['base_form_id']], $form, $form_state, $form_id);
$this->themeManager->alter(['form_' . $build_info['base_form_id']], $form, $form_state, $form_id);
// hook_form_FORM_ID_alter
$this->moduleHandler->alter(['form_' . $form_id], $form, $form_state, $form_id);
$this->themeManager->alter(['form_' . $form_id], $form, $form_state, $form_id);
what do you think?