Drupal Tips: Altering order of execution in resources

Written on January 15, 2022

Category: Drupal Tips

Author: David Rodríguez, @davidjguru

Picture from Unsplash, by @andretaissin
Picture from Unsplash, user Andre Taissin, @andretaissin

Changes, changes, changes… throughout a project, we have to implement many changes and alterations to the Drupal code, without touching it directly -of course-. The event system is very powerful but there are still hooks at all levels, so sometimes we must use Hooks that will alter things already modified by other Hooks of the same nature. Also we may need to execute our changes before the changes made by another module (contrib or custom), and for that we must understand well how the order of execution of resources works…

In this brief publication for this sketchbook, I’ve put together some quick techniques for making modifications to the run order.** What can we do to add new submit functions to a form? (incorporating new actions to that submit), How can we make our submit actions precede existing ones? What is the relationship between the “weight” assigned to a module and its position in the execution order? in an intuitive way I want to make an approach to these questions and along the way, share some useful resources, such as functions and available contributed modules that we can use for our changes.
Read this little post, it can be useful if you need execute this kind of changes.

Acknowledgments

This post was composed from my current position as Senior Drupal Developer at Digitalist Sweden, one of the biggest companies oriented to Open Source in Europe and specifically focused in Drupal.


TL;DR -> This is a little post about how to do changes in the order of execution of some Drupal resources by altering the position or weight of modules, hooks and other functions like a submit for forms.


Table of Contents.

1- Altering order in submit functions
2- Altering the priority order of modules
3- How to change the registry of modules using a hook
4- How to change the registry of modules using a contrib module
5- :wq!


1- Altering order in submit functions

Sometimes, it’s very important for us to alter the order of execution in certain types of submit functions. By instance, we’re using a previously existing form with its existing submit fuction, but we want alter the process by using our own hook. And inside this hook we can define a new submit fuction for the form: We need 1) Add a new submit function and 2) Alter the order and have our submit executed first.

Let’s see a more specific use case.

Use Case

We have custom changes in one entity (e.g a new field in the menu_link_content entity type) related with a node and loaded when we’re creating a new node by UI in Drupal:

Creating a menu entry when creating a new node in Drupal

In this case, just when asking for a menu entry wen we’re creating a new node in Drupal we’re always using the MenuLinkContent entity. Our change is as follow: We have a new field (a custom check for visibility) in this kind of Entity and we need to load/update the new field just when the node + menu_link_content are created and others modules are working on it (specifically the menu_ui module from Drupal Core). So we have to pass values for our new field, due to the original hook is not responsible for loading our new field (and it doesn’t know anything about it). So the main idea is using a form_alter Hook and then add a new submit callback function just when the node form is saved.

First I need to create my new submit function in a new custom module. For this step I’ll use the scheme of the pre-existing function menu_ui_form_node_submit within the menu_ui.module file. The key step here is: while we’re saving a node, the menu_link_content entity was already created and existing in database register.

/**
 * Form submission handler for menu item field on the node form.
 *
 * @see my_custom_module_form_node_form_alter()
 */
function my_custom_module_form_node_form_submit($form, FormStateInterface $form_state) {
    // Get the current node information and search the entity in database.
    $node = $form_state->getFormObject()->getEntity();
    $query = \Drupal::entityQuery('menu_link_content')
      ->condition('link__uri', 'entity:node/' . $node->id());
    $entity_id = $query->execute();
    
    // Load the existing entity menu_link_content.
    $entity = \Drupal::entityTypeManager()->getStorage('menu_link_content')
    ->load(array_key_first($entity_id));
    
    $entity->check_visibility->value = $form_state->getValues()['menu']['profile']['check_visibility'];
    $entity->save();
    }

Then in my new custom module I’ll create a new form_alter_hook. My goal is open the catalog of actions associated to the required form (node_form) and alter the order in submit callbacks. In order to do this, I’m filtering the type of actions that I’m interested to and then unsetting / setting the items.

/**
 * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
 *
 * Adds stuff to the node form.
 *
 * @see my_custom_module_form_node_form_submit()
 */
function my_custom_module_form_node_form_alter(&$form, FormStateInterface $form_state) {  
[...]

  foreach (array_keys($form['actions']) as $action) {
    if ($action != 'preview' && isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') {
        // Delete the former last item in the array of submit callbacks.
        array_pop($form['actions'][$action]['#submit']);
        // Now load the new order including the new custom submit action.
        $form['actions'][$action]['#submit'][] = 'my_custom_module_form_node_form_submit';
        // Reload the former submit function from the original core module menu_ui.
        $form['actions'][$action]['#submit'][] = 'menu_ui_form_node_form_submit';
    }
  }

[...] 
}

So, just when I’m saving a new node I’m reviewing the value of my new custom field “check_visibility”, getting from the form_state of the node form and saving the value in the menu_link_content entity. Then the next submit from the core module “menu_ui” goes on to run.

2- Altering the priority order of modules

In Drupal, the order of execution of a hook is related to the weight assigned to the module that contains it. Each module (custom, contrib, core) has assigned a “weight” value that can be located in the “system” table of the database and in the related configuration file “core.extension.yml” in config/sync/:

  typed_data: 0
  update: 0
  user: 0
  varnish_purger: 0
  views_ui: 0
  workflows: 0
  pathauto: 1
  content_translation: 10
  views: 10
  paragraphs: 11

You can set a low weight to get your module to execute after or before others. If no weight is defined modules get a default weight of 0 (the default value for core modules in config).

To find out the order in which the modules are executed you can get info using the configFactory service, by doing:

$extension_config = \Drupal::configFactory()->getEditable('core.extension');
$module_weight = $extension_config->get("module.MODULE_NAME"); 

Now we’re going to set a weight value for our custom module, inside a .install file, from the hook_install(). We can sue an existing function called ‘module_set_weight()’ available within the Drupal API present in module.inc, with functions oriented to interacting with Drupal modules. See this API here.
doing something like this:

/**
 * Implements hook_install().
 */
function my_custom_module_install() {
  [...]
  // Ensures the hooks contained in your module will be executed after the originals.
  // e.g. the hook menu_ui_form_node_form_alter()
  // Note: Weight for uninstalled modules can't be changed. 
  module_set_weight('my_custom_module', 100);
  [...]
}

Don’t forget update config, passing the active configuration (from database) to the passive configuration (configuration files) in order to have all the config values updated in your Drupal installation:

$ drush cex -y

(And don’t forget commit the new changes to the remote repository, of course).

3- How to change the registry of modules using a Hook

As you know, there are a lot de Hooks in Drupal, that is, predefined functions that cause alterations in in the normal operation of the installation, modifying aspects of the Drupal core or other modules. Ok you can check here all the available Hooks in Drupal 9.x. Ok but is there any Hook that can be useful for us? Yep, the hook_module_implements_alter that implements just the steps we’ve seen before: pull out of the array the indicated hook for a selected module and then save the same items at the end of the array (just like in the case of the submit functionts in the first section).

Exactly, this hook is executing:

function hook_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'form_alter') {
    $group = $implementations['my_custom_module'];
    unset($implementations['my_custom_module']);
    $implementations['my_custom_module'] = $group;
  }
}

This is very useful to specify specifically which hook we want to alter at what time and for which module, locating the module by its name within the array of existing implementations.

4- How to change the registry of modules using a contrib module

There is also a contributed module that acts as a wrapper for this type of functionality but also provides several drush subcommands to execute and manage its features from the command line: The Modules Weight contrib module can change the order by GUI and by drush commands. Let’s see.

First, a fast view in my config/sync/core.extension.yml file, with some core modules:

Initial view of the core.extension config file

Now I’m gonna installing and enabling the module in my DDEV deploy for testing:

$ ddev composer require drupal/modules_weight
  
  Using version ^1.9 for drupal/modules_weight
  [...]

$ ddev drush en -y modules_weight
$ ddev drush cr

And now from GUI present in /admin/config/system/modules-weight, I will change the order of the admin_toolbar family modules, as you can see:

Changing values from GUI

And after saving the page, Now I can see the changes in the same config/sync/core.extension.yml file (after drush cex):

Review the weight changes from the same config file

I have been testing the set of drush sub-commands provided by the module, but they don’t seem to work on my installed version of Drush (11.0.0 in Drupal 9.3.2) :-(

5- :wq!


Written on January 15, 2022