Drupal 8: Altering Hook Weights

Drupal's hook system allows modules to interact with various parts of a Drupal application and is part of the power of the application.

One common issue I have found is altering things that have been added by other modules during the hooks process. For example, a hook might be called that involves gathering data from one or more modules. If you need to intercept this configuration there is no guarantee that your module will be called after the module you are trying to intercept.

Another (more concrete) example is when altering forms. The code below shows a hook_form_alter() being run on all node edit forms on a site.

<?php

use Drupal\Core\Form\FormStateInterface;

/**
 * Implements hook_form_alter().
 */
function my_module_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  if (isset($form['#entity_type']) && $form['#entity_type'] == 'node') {
    if (isset($form['source_langcode'])) {
      // Hide the 'Source language' dialog on node edit pages.
      $form['source_langcode']['#access'] = FALSE;
    }
  }
}

The source_language form field being altered here is added by the content_translation module and I found it was often added after this hook, which meant the hook actually did nothing as the source_langcode field wasn't present.

The solution here is to use another hook to alter the order of elements in the hook processor so that our hook was processed after the hook that adds this form element. The hook_module_implements_alter() hook is used to accomplish this.

In the example below we look for the presence of the hook implementation in the form_alter hook. If we find it then we copy it to the end of the array. This has the effect of allowing our hook to run after all other hooks that implement the same hook.

/**
 * Implements hook_module_implements_alter().
 */
function my_module_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'form_alter' && isset($implementations['my_module'])) {
    // Move this module's implementation of form_alter to the end of the list.
    // We are doing this so that the my_module_form_node_page_form_alter
    // function is called AFTER ContentTranslationHandler::entityFormAlter
    // which contains the code we are trying to alter.
    $hookInit = $implementations['my_module'];
    unset($implementations['my_module']);
    $implementations['my_module'] = $hookInit;
  }
}

Another example was altering the page_top array in the hook_page_top() hook. What I was trying to do here was remove the view_mode form of the node_preview area as (at the time) we didn't want users changing the view type of previews. As it happens the node_preview form was added into the hook_page_top() hook in the node module and so it was essential to run our implementation of that hook after the node module in order to intercept the form correctly.

Here is the code in full.

/**
 * Implements hook_page_top().
 */
function my_module_page_top(array &$page_top) {
  if (isset($page_top['page_top']['node_preview'])) {
    unset($page_top['page_top']['node_preview']['view_mode']);
  }
}

/**
 * Implements hook_module_implements_alter().
 */
function my_module_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'page_top') {
    // Move this module's implementation of hook_page_top to the end of the list.
    $hookInit = $implementations['my_module'];
    unset($implementations['my_module']);
    $implementations['my_module'] = $hookInit;
  }
}

 

Add new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
11 + 0 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.