Loosely-coupled module integration

Drupal often makes certain things so easy to do, that we as developers don’t take the time to consider the alternatives. Lately I’ve been giving a lot of thought to the module dependency system and possible alternatives. The main reason for this is that dependencies create a more brittle overall system which in turn makes the code more difficult to maintain and re-use.

Scenario 1

I’ve seen many install profiles and modules that include modules like overlay or toolbar as a dependency. In almost every case, this was done so that those modules would be installed by default. The problem with using the dependency system is that these modules cannot be removed without modifying the code. If you wanted to use admin_menu in place of toolbar, you would need to first remove the dependency from the info file.

Solution

This one has a simple solution. Rather than using dependencies to install modules, use a simple install hook with module_enable.

/**
 * Implements hook_install().
 */

function example_install() {
  // Enable optional modules.
  module_enable(‘toolbar’, ‘overlay’);
}

Scenario 2

Say we have a module that creates a basic article node, with a title, body and taxonomy field for tags. Typically we would make taxonomy module a dependency of the article module. In the spirit of reusable code, we want to make our article module as self-contained as possible, so that we can use it on all of our Drupal projects.

But what if we have a project that has no need for taxonomy? Must we always enable the taxonomy module to use our article module, even if there is no need?

Solution

The answer to our problem comes in the form of a couple of new hooks for Drupal 7: hook_modules_enabled and hook_modules_disabled, give us the perfect opportunity to act upon new dependencies being enabled and disabled.

First we will create a helper function for our optional functionality. In this case, we want to create a tags vocabulary and add a taxonomy reference field to the article node type, only if the taxonomy module is available.

/**
 * Add the "tags" taxonomy field to a node type.
 */

function article_add_tags_field($type) {
  if (module_exists(‘taxonomy’)) {
    // create the tags vocabulary and add the field to the bundle, making sure
    // to check if they already exist.
  }
}

Next, we implement hook_enable to create our article node type. This technique works equally well with hook_install so choose the hook that is most appropriate for your situation. Notice that we are checking the existence of the taxonomy module before calling our helper function. This check could be skipped since it also happens in the helper, but I think checking it here reinforces the idea that this is an optional feature.

/**
 * Implements hook_enable().
 */

function article_enable() {
  // Borrowed from standard.install.
  $type = node_type_set_defaults#40;array(
    ‘type’ => ‘article’,
    ‘name’ => st(‘Article’),
    ‘base’ => ‘node_content’,
    ‘description’ => st(‘Use <em>articles</em> for time-sensitive content…’),
    ‘custom’ => 1,
    ‘modified’ => 1,
    ‘locked’ => 0,
  ));
  node_type_save($type);
  node_add_body_field($type);
  if (module_exists(‘taxonomy’)) {
    article_add_tags_field($type);
  }
}

Lastly, we implement hook_modules_enabled. This hook is called by the module system any time one or more modules are enabled. Here is where we have the opportunity to look for newly enabled modules that our module knows how to integrate with. If the taxonomy module is in the list of modules that have been enabled, we can fire our helper function.

/**
 * Implements hook_modules_enabled().
 */

function article_modules_enabled($modules) {
  if (in_array(‘taxonomy’, $modules)) {
    article_add_tags_field(node_type_load(‘article’));
  }
}

For completeness, you should probably also implement hook_disable and hook_modules_disabled, but I’ll leave that as an exercise for you.

We are using these two methods to keep our dependency chain light and add in optional functionality on our Drupal 7 projects. I think there is an opportunity to build true support for these types of optional integrations into Drupal 8. I’d love to hear how others are handling this situation. How are you handling dependencies and integrations in your modules, especially ones meant to be used in a variety of contexts and situations?