Drupal 8: Altering Update Dependencies

I perform a lot of Drupal updates, and they mostly go very well. The code updates without incident and the database updates apply whatever updates they need without a hitch. Every now and then, however, I will come across a project that hasn't received updates in a while, which means there are a lot of database updates to run through.

Having Drupal run 100+ database updates across modules and core can be a bit problematic. Some modules will require core updates to happen first, and when this happens out of order the database update update fails spectacularly.

One way of solving this is to juggle the versions of modules and Drupal around until you have something that works, but this can be quite a challenge and can leave you into performing multiple deployments with potential downtime just to get things up to date. This update situation can be complicated if you maintain a contributed module and you rely on update hooks in core or modules to be run before your module can perform its updates.

These problems can be solved by using the hook_update_dependencies() hook. This hook will must be placed in a module .install file, and allows you to force certain update hooks to run before others. It takes no parameters and must return an associative array of module dependencies.

Here is the basic footprint of the hook.

/**
 * Implements hook_update_dependencies().
 */
function my_module_update_dependencies() {
  $dependencies = [];

  return $dependencies;
}

How do we create the module dependencies array? Let's say that we discovered that some custom configuration we created caused an upstream problem with the upgrade process of the Webform module. We therefore need to fix that configuration before the update hook on the Webform module.

Let's create an update hook that sorts out the configuration.

/**
  * Fix broken configuration.
  */
function my_module_update_8001() {
   $config = \Drupal::configFactory();
   // Fix broken configuration here...
}

We still need to ensure that this hook runs before the hook in the Webform module so we create a hook_update_dependencies() hook in order to run our update hook before the one in Webform. Note that the update hook webform_update_8501() doesn't exist, it's just an example update hook in a commonly used module.

/**
 * Implements hook_update_dependencies().
 */
function my_module_update_dependencies() {
  $dependencies = [];

  // Ensure the my_module_update_8001 update runs before webform_update_8501.
  $dependencies['webform'][8501]['my_module'] = 8001;

  return $dependencies;
}

The basic syntax of this dependency array is as follows.

$dependencies['RUN THIS'][HOOK ID]['AFTER THIS'] = HOOK ID;

You can build up a number of these dependencies in this hook to target different modules. It is also possible to apply these dependencies to any available hook in your site, you aren't restricted to hooks without your own module.

Real World Usage

This hook is used in Drupal contributed code in order to ensure that updates that modules rely on are run first.

For example, here is the hook being used by the Webform module to ensure that certain fields exist in tables (created by system updates) before the Webform module performs its own updates.

/**
 * Implements hook_update_dependencies().
 */
function webform_update_dependencies() {
  // Ensure that system_update_8501() runs before the webform update, so that
  // the new revision_default field is installed in the correct table.
  // @see https://www.drupal.org/project/webform/issues/2958102
  $dependencies['webform'][8099]['system'] = 8501;

  return $dependencies;
}

Another example is in the Facets module, which is used to ensure that a block_content update hook runs before a facets module update hook. In this instance they are also checking for a specific version of Drupal being used.

/**
 * Implements hook_update_dependencies().
 */
function facets_update_dependencies() {
  $dependencies = [];

  if (version_compare(\Drupal::VERSION, '8.6', '>=')
    && \Drupal::service('module_handler')->moduleExists('block_content')
  ) {
    // block_content_update_8600() adds some fields to Blocks that makes
    // facets_update_8006() fail if upgraded at the same time.
    $dependencies['facets'][8006] = [
      'block_content' => 8600,
    ];
  }

  return $dependencies;
}

Conclusion

One word of warning here is that the need to use this hook to allow updates to work can mean that the site being worked on has some problems. I have found on projects in the past that this hook is a good way to work around incomplete upgrades.

For example, if the code was updated, the database updates run, and then the code downgraded again without removing newly created tables then a subsequent upgrade will error when trying to re-create these tables. Using custom update hooks to remove tables and then using the hook_update_dependencies() hook is a good way to clear the way fore these updates to run.

That said, this is a good way of ensuring that the update process runs in a consistent and predictable manner, especially when performing large and complex updates involving many modules.

More in this series

Add new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
14 + 1 =
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.