Author image
Senior Consultant

Creating Custom Hooks in Drupal

Anyone familiar with developing with Drupal will be very familiar with how we use hooks to leverage functionality provided by other modules and the core itself. We use them as part of the course when it comes to wanting to tweak data or even extend some core functionality, but how do we expose our own hooks for others to be able to alter what our custom modules do without the need for a developer having to hack around with our code?

As you might expect, defining hooks in Drupal is actual pretty straightforward and the first question to ask is what do we want our hook to do as this defines how we have to declare it in our module.

Altering Data

Perhaps the simplest use of a hook would be to allow others to change the data our code is working with, so let’s say for example we have a simple bit of code that uses an array of data to do something exciting. It’s not beyond the realms of possibility another module might want to take advantage of this functionality so it makes sense to allow other modules to alter this array. Let’s start by looking at how things might look before we implement a hook to extend it.

function example_list_generate(){
  $data = array(
    ‘name’ => t(‘An Example Name’),
    ‘message’ => t(‘An Example Message’),
  );

  //Do something cool with the $data array
}

So there’s not much going on in this example, basically we define an array of data then we do something with it. Now suppose we want to allow other modules to alter this array so they can either change what we've already defined or add to it. This is where a hook implementation comes into play using the drupal_alter() function, so let’s change things slightly to show how this would look.

function example_list_generate(){
  $data = array(
    ‘name’ => t(‘An Example Name’),
    ‘message’ => t(‘An Example Message’),
  );

  // Calling all modules implementing hook_my_hook_alter():
  drupal_alter('my_hook', $data);

  //Do something cool with the $data array
}

The drupal_alter() function takes two arguments, the first one is the name of the hook we want to invoke and the second is the data we want to alter. So all this is doing is running through all implementations of hook_my_hook_alter passing the $data array by reference and allow each implementation to do something with it. It is important to note that we need to add the _alter bit to the end of each hook implementation we declare in other modules for this to work. Below is a really simple example of an implementation of this hook which just adds another element to the $data array.

function example2_my_hook_alter(&$data){
  $data[] = array(
    ‘name’ => t(‘An Example Name from Hook’),
    ‘message’ => t(‘An Example Message from Hook’),
  );
}

Notice this function expects the variable to be passed by reference so we can edit it, and that's all there is to it when it comes to altering data using hooks.

Extending Functionality

Obviously we use hooks for much more than just altering the data that functions have to work with, in many cases we actually want to extend this functionality for ourselves and there are a few ways in which we can achieve this. We'll start with the simplest using module_invoke_all() which will simply invoke all hooks with a given name. Let's take our example above, but this time instead of altering the data let's add some functionality.

function example_list_generate(){
  // Calling all modules implementing hook_my_hook():
  module_invoke_all('my_hook');
}

As we're not altering data this time when we call the hook from another module we don't need to append the _alter() bit to our hook implementations so an example implementation might be something like:

function example2_my_hook(){
  drupal_set_message(t('Inside our custom hook'));
}

Now this obviously isn't doing anything exciting; it's just outputting a standard Drupal status message to the screen but the principle is the same whatever functionality we want to add.

A minor extension of this is the use of the module_invoke() function which we can use to invoke a hook in a particular module rather than every implementation defined. If we were to change our function above to just call the implementation of hook_my_hook inside example2.module it would look like:

function example_list_generate(){
  // Calling the example2 module implementation hook_my_hook():
  module_invoke('example2', 'my_hook');
}

So this is all very well and good if we just want to add on a bit of extra functionality but often we want our hooks to do something then return its results to the original module. module_invoke() and module_invoke_all() don't allow us to do this without a bit of extra glue.

Let's consider the example above where we just output a standard Drupal status message but this time let's get it to return some data to the original module that invoked it. The code for the original module might look something like:

function example_list_generate(){
  $result = array();
  foreach (module_implements('my_hook') as $module) {
    // Calling all modules implementing hook_my_hook and saves their results
    $result[] = module_invoke($module, 'my_hook');
  }
}

This time instead of just using module_invoke_all() we use module_implements() to get an array of all the modules that invoke a particular hook and then loop through these using module_invoke() to invoke each module in turn; this time appending any returned results to an array called $result. So for example we might have an implementation of hook_my_hook() that does something then returns that result for further computation in the original function like:

function example2_my_hook(){
  global $user;
  return $user->name;
}

So in this example implementation of hook_my_hook() is just returning the user name of the current user but the concept is the same whatever we want to do to produce the return result of the function. It then gets added to the $results array in the original module for that function to do whatever it needs to with it.

Finally we might want a hook that not only triggers functionality but also sends through arguments. This is done in a very similar way to examples above; however, this time we need to define the arguments we send through to the hook implementations in other modules. An example of this is shown below:

function example_list_generate(){
  foreach (module_implements('my_hook') as $module) {
    $function = $module . '_my_hook';
    // will call all modules implementing hook_my_hook
    // and can pass each argument as reference
    $function($arg1, $arg2);
  }
}

With the resulting hook implementation in another module looking something like:

function example2_my_hook(&$arg1, &$arg2){
  //Do some stuff
}

All this is doing is using the module_implements() function to get a list of all the modules that implement hook_my_hook then iterating through these to define a function name taken to be $module concatenated with _my_hook (which is logical given this is how we declare hook implementations in our modules) and then calling this function with the required arguments, in this example we pass arguments through by reference meaning we can alter the arguments in each of our implementations of hook_my_hook() (the order modules run in is defined by the module's weight in the system table, which is why occasionally we get unexpected results when we try to alter arguments as other modules have yet to apply their magic).

And that's basically all there is to know about how to create your own hooks in Drupal. It's actually pretty straightforward and is the ideal way to stop anyone having to hack around with your code as well as making sure you can make the most out of your functionality without having to rewrite it every time!

Drupal 8

With the advent of Drupal 8 much of what is detailed above about implementing hooks no longer remains true. Hooks (except many alter hooks) have nearly exclusively been replaced with the more object oriented approach of using events. The logic remains the same, we fire events to extend functionality in exactly the same way we did using the procedural approach in Drupal 7; however, the implementation has completely changed. We'll include a follow up article to explain all about how and why to do this in Drupal 8!