20 APIs in 20 Days: The Hooks API and Custom Modules

Apr
01

This post is an entry in our 20 APIs in 20 Days series. Learn more about how best practices lead to sustainable development at www.trellon.com.

Ask someone what a 'hook' is, and their answer can tell you quite a lot about the person. The word gets used to describe things related to pirates, music, characters in campfire stories, aeronautics, etc. The word also has a specific meaning in programming, however, and serves as an important structural concept in most modern programming languages and frameworks.

Most Drupal developers are well acquainted with Drupal's hook system. Hooks are triggers that operate when certain events occur in the page generation process. They are capable of modifying data while it is being processed and carrying out other actions in response to certain conditions. Some of the more common hooks operate when a node loads, when a form is generated, or when someone saves user information. This part of the 20 APIs in 20 Days series provides an overview of the nature of hooks and how they operate as the foundation of most custom modules.

What Does the Hooks API Do?

For those who are familiar with object oriented programming, hooks are conceptually related to the idea of a method. They present standard ways of interacting with data structures that operate independently of specific callbacks. Unlike traditional OO constructs, hooks are not tied to specific objects, they are tied to procedural points in the page generation process. Also, many hooks are state aware, and do different things depending on what exactly is happening at the time the hook is called.

For those with the good sense to avoid the religious war that is OO programming, here is what you really need to know about what Drupal's Hooks API does:

  • A Hook is a PHP function in a custom module that executes code in response to events or conditions while a page is being generated.
  • Hooks allow modules to interact with other modules, including the Drupal core modules.
  • The Hooks API provides a common naming standard for hooks, allowing developers to implement them in custom modules without needing to engage in extensive programming.
  • The Hooks API allows developers to create their own hooks that other modules can access, that leverage the same techniques used in core Drupal.
  • Many hooks are state aware, and operate differently depending on what Drupal is actually doing when the hook is triggered.

The important thing to understand about this model is it allows developers to modify the operations of Drupal in many small ways instead of having all that code written up in a single place. Having the Hooks API in place means someone can add a module to Drupal to expand the functionality of the system without needing to rewrite any code. It is what we mean when we say that Drupal is modular.

Let's look at this another way. There is a hook in Drupal core called hook_nodeapi. It fires each and every time a node loads in a Drupal site (among other operations, which we will delve into). Loading the node is what we would call an event, it causes hook_nodeapi to look through all the installed modules where hook_nodeapi is registered. Like a series of dominos, Drupal is going to execute the code contained in each hook_nodeapi function in each module, and the result will be a fully formed node that is ready to present to users.

The location module, for instance, uses hook_nodeapi to add address information to nodes when they are loaded. It also saves address information when the node is saved. This is what we mean when we say that many hooks are state aware, they know if you are saving something, or loading something, or deleting something, etc.

Despite their importance, there is sometimes a terribly small amount of documentation about many hooks in Drupal, and this can make learning about them a challenge. For most people, the path to understanding hooks is by reading through people's code and implementing them yourself within your own projects. Also, there are very few people who really understand what all the hooks do in Drupal core or their real implications. At Trellon, when we hire a new developer, we ask them a series of increasingly difficult questions about hooks. In six years of interviews, no one has ever provided a satisfactory answer for what hook_comment does - despite the fact it seems so obvious and has been around for a long time. This speaks to the previous point about how you learn about hooks, people don't find many reasons to implement hook_comment and thus don't always know that much about it.

Recognizing a Hook in the Wild

Hooks have a common naming structure that can be easily recognized once you know how to look for it.

Developers use a PHP function in their code with a special name to register a hook. The special name is constructed this way:

  • The name of the module;
  • An underscore;
  • Followed by the name of the hook

For example, if someone had a module called foo, and they wanted to implement hook_bar, they would put a function in the foo module called foo_bar. It's really not that hard to implement a hook.

Passing in the appropriate data, however, is another matter. Each hook is a PHP function and has it's own arguments for handling data. Many hooks also pass variables in by reference, in order to modify data before it is passed back to Drupal for further processing.

A common mistake custom module developers make is to put in the wrong set of arguments. A handy trick is to keep api.drupal.org open in another window and copy out the examples from the documentation for the hook. This can save a lot of time for people who are just getting started.

There are some hooks that are used more often than others, and are key to creating custom modules. Reading through the code inside these hooks in any custom module will tell you a lot about what the module does and how it does it. These common hooks include:

  • hook_help - Create help text associated with your custom module.
  • hook_perm - Create permissions associated with your module.
  • hook_menu - Define menu items and page callbacks.
  • hook_menu_alter - Alter the data being saved to the {menu_router} table after hook_menu is invoked.
  • hook_nodeapi - Act on nodes defined by other modules.
  • hook_block - Declare a block or set of blocks.
  • hook_cron - Used for carrying out actions when the cron runs.
  • hook_user - Act on user account actions.
  • hook_form - Display a node editing form.
  • hook_form_alter - Perform alterations before a form is rendered.
  • hook_form_FORM_ID_alter - Provide a form-specific alteration instead of the global
  • hook_theme - Register a module (or theme's) theme implementations.
  • hook_link - Define internal Drupal links.
  • hook_link_alter - Perform alterations before links on a node are rendered.

There is a full list of hooks in Drupal core available at http://api.drupal.org/api/group/hooks. For advanced developers, understanding the implications of certain hooks does become important. Hook_db_rewrite_sql is one that gets used every once in a while, and is generally implemented to provide access restrictions to content on a site. It's one of those ones you want to watch out for, because some modules that implement it can turn out to be incompatible. For instance, organic groups and domain access both use hook_db_rewrite_sql to control access to content, and there are situations where they are not going to work together. This is what is meant by 'implications,' having 2 modules installed that create access restrictions can really gum up the works!

How to Implement a Hook

Implementing a hook in your code is really as easy as giving it the right name, passing in the right arguments, and running whatever code you need within the hook to make it work. There are various modules that will build out sets of hooks for you to get you started (we don't want to go into them here, but Google around and you will quickly find some).

Here are some examples of hooks in action:

Example 1: Alter links on a node before they are rendered:

<?php
/**
* Implementation of hook_link_alter().
*/
function mymodulename_link_alter(&$links, $node) {
  global
$user;
 
// Modify the forward link for logged user's nodes
 
if ($user->uid == $node->uid && isset($links['forward_links'])) {
   
$links['forward_links']['href'] = 'resume/'. $node->nid .'/send';
   
$links['forward_links']['query'] = '';
  }
}
?>

Example 2: Contact module's page menu callback implementation:

<?php
/**
* Implementation of hook_menu().
*/
function contact_menu() {
 
$items['contact'] = array(
   
'title' => 'Contact',
   
'page callback' => 'contact_site_page',
   
'access arguments' => array('access site-wide contact form'),
   
'type' => MENU_SUGGESTED_ITEM,
   
'file' => 'contact.pages.inc',
  );
  return
$items;
}
?>

Creating Your Own Hooks

You can allow other developers to extend your module's functionality by exposing your own hooks. The simplest way to expose your "hook" is to call the function module_invoke_all('myhookname') (http://api.drupal.org/api/function/module_invoke_all/6) in your module. Then a fellow developer can implement this hook creating a function mymodulename_myhookname(). Let's demonstrate this on the next example.

File: mymodulename.module

<?php
/**
* Sample module function with my own hook.
*/
function mymodulename_foo() {

  // Example variable $layouts
 
$layouts = array();

  // Let's call the hook 'mymodulename_layouts'
  // The hook will hook other modules on our function
 
$layouts = module_invoke_all('mymodulename_layouts');

  // The returned value is an array of return values of the hook implementations
 
foreach($layouts as $layout){
   
// Do stuff with the returned variables
   
print $layout;
  }
}
?>

As you probably noticed, we called our hook mymodulename_layouts - it includes the 'mymodulename' string. This is preventing us from interference with other modules - if we just called the hook 'layouts', there could be another module which is exposing hook with the same name.

To test the hook we just created, we will create another module - let's call the module 'test' and put the following function into it:

File: test.module

<?php
/**
* Implementation of hook_mymodulename_layouts().
*/
function test_mymodulename_layouts() {
 
$layout = 'testing';
  return
$layout;
}
?>

The function module_invoke_all() returns array of return values of the hook implementations. If modules return arrays from their implementations, those are merged into one array. You can also include arguments in your hook, or you can also pass them as a reference, as an example you can take a look at hook_nodeapi, http://api.drupal.org/api/function/hook_nodeapi.

When creating complex modules, these functions can be useful to you:

  • module_hook - Determine whether a module implements a hook.
  • module_implements - Determine which modules are implementing a hook.
  • module_invoke - Invoke a hook in a particular module.
  • module_invoke_all - As we described above, this function invokes a hook in all enabled modules that implement it.

If this part of the 20 APIs in 20 days was interesting for you, can visit the hooks API documentation for further information at http://api.drupal.org/api/group/hooks. I hope you enjoy the rest of the series!

10 Comments

Hell ya! Nice article!

Hell ya! Nice article!

Thanks Shawngo!

Thanks Shawngo!

Kudos to you for pointing out

Kudos to you for pointing out the down side that one problem with hooks is that multiple modules acting on a hook can create incompatible results. I wish there was some way to easily know which modules affect which hooks so that possible collisions could be known about easily ahead of time.

There is a simple way of

There is a simple way of knowing which modules returned which results from a hook. Something like this does the trick:

foreach (module_implements('myhook') as $module) {
  $results[$module] = call_user_func_array($module .'_myhook', array($arg1, $arg2));
}

Try 'drush hook ' e.g. "drush

Try 'drush hook ' e.g. "drush hook menu"

BTW the hook command requires

BTW the hook command requires you to install the 'devel' module.

Multiple node access

Multiple node access implementations may be one of the only cases where different implementations of a hook can be incompatible. Or _seem_ incompatible might be a better way to say it. To get multiple node access systems working together you would use the Module Grants module:
http://drupal.org/project/module_grants

Wooohooo! Good job Valdo ;)

Wooohooo! Good job Valdo ;)

i need hooks for data

i need hooks for data module.
i want to call the delete function from data module into my custom module.how could i call it.please help me in this regard.
thanks in advance,
please help me

Excellent tutorial

Excellent tutorial

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.

More information about formatting options