I’m a big fan of Drupal, possibly the biggest in our office. Over the past seven years, I’ve been thoroughly indoctrinated into all things best practice, patched inherited hacks, learned how to replace core components in ways that ensure the site can still auto update, and generally, just stretched the limit of what Drupal can do. So I thought it’d be a good time to step back and review just what it is I use Drupal for most often in custom development. I’ll start by giving some statistics on what hooks I use most, why. Then, just for fun, I’ll cover the the more rare cases.

Hook Usage Statistics

HookPercentage of projects
hook_menu93.75
hook_permission87.5
hook_form_{form_id}_alter81.25
hook_node_view75
hook_block_view75
hook_block_info75
hook_wysiwyg_editor_settings_alter62.5
hook_views_pre_render62.5
hook_views_api62.5
hook_theme62.5
hook_node_insert62.5
hook_node_update56.25
hook_node_presave56.25
hook_flush_caches50
hook_node_load43.75
hook_menu_alter43.75
hook_query_TAG_alter37.5
hook_init37.5
hook_html_head_alter31.25
hook_field_extra_fields31.25
hook_field_attach_form31.25
hook_entity_info_alter31.25
hook_user_insert25
hook_mail25
hook_views_default_views18.75
hook_user_update18.75
hook_node_delete18.75
hook_drush_command18.75
hook_cron18.75
hook_block_save18.75
hook_block_configure18.75
hook_user_login12.5
hook_services_resources_alter12.5
hook_services_resources12.5
hook_rules_action_info12.5
hook_preprocess_page12.5
hook_node_validate12.5
hook_node_access12.5
hook_mail_alter12.5
hook_form_alter12.5
hook_filter_info12.5
hook_exit12.5
hook_boot12.5
hook_votingapi_results6.25
hook_views_query_alter6.25
hook_views_handlers6.25
hook_username_alter6.25
hook_user_view6.25
hook_uninstall6.25
hook_services_request_preprocess_alter6.25
hook_schema6.25
hook_registration_entity_settings6.25
hook_registration_access6.25
hook_openlayers_styles6.25
hook_openlayers_behaviors6.25
hook_node_page_view6.25
hook_node_alter6.25
hook_install6.25
hook_fivestar_widgets6.25
hook_field_formatter_info6.25
hook_entity_update6.25
hook_entity_insert6.25
hook_entity_delete6.25
hook_default_rules_configuration6.25
hook_ctools_plugin_api6.25
hook_cron_queue_info6.25
hook_commerce_checkout_pane_info6.25

Hook Purposes

1. hook_menu

https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_menu/7

This is the base for nearly any site with custom functionality. We also tend to include a few pages standard on any site we touch such as a Style Guide. Make sure you understand all of the menu ‘type’ constants available before you do much here. If you use this to introduce any new admin sections, the following snippet will make your /admin/{section} easy.

[php]
 // Admin
 $items[‘admin/{section}] = array(
   ‘title’ => {Name},
   ‘description’ => ‘{Description}.’,
   ‘position’ => ‘left’,
   ‘weight’ => -8,
   // This is the magic that auto builds for you
   ‘page callback’ => ‘system_admin_menu_block_page’,
   ‘access callback’ => ‘_custom_access_{section}_administration’,
   // And this is required to reach that function
   ‘file path’ => ‘modules/system/’,
   ‘file’ => ‘system.admin.inc’,
 );
[/php]

2. hook_permission

https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_permission/7

Nearly* every site with a custom section is going to require unique permissions. When you can, stick to the system permissions already in existence to keep things simple to understand (you get node specific permissions automatically!), but this will let you do more crafty controlling.

3. hook_form_{form_id}_alter

https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_form_FORM_ID_alter/7

I love this one, so very much. I have to count it as a single hook for the purposes of percent calculation, but I’m more likely to have 5+ of these in any given project. For those not familiar, this is the upgraded version of hook_form_alter. hook_form_later has to be run on every single form on the site, and that can cost you performance. With only a minor change change you can limit your system’s overhead. It’s absolutely worth the minor amount of effort. Need to find the form_id? Right click the form on the front end, inspect, and check the hidden fields near the top of the

.

4a. hook_node_view

https://api.drupal.org/api/drupal/modules%21node%21node.api.php/function/hook_node_view/7

Node view is interesting. I didn’t expect it to be so high on the list. I’ve changed why I use it the last couple years. At first, I was sticking all sorts of page and node specific markup into the object’s content directly. But, that lead to issues when I wanted to change themes on sites. More recently, I simply load content that needs to be rendered by the theme layer into variables, and my own custom theme additions handle the display portion.

4b. hook_block_info

https://api.drupal.org/api/drupal/modules%21block%21block.api.php/function/hook_block_info/7

4c hook_block_view

https://api.drupal.org/api/drupal/modules%21block%21block.api.php/function/hook_block_view/7

Not a lot to comment here. We need blocks. Get blocks. Let’s continue.

5a. hook_wysiwyg_editor_settings_alter

This one’s purpose has changed over time. Initially, when we were deploying older versions of tiny or ckeditor with the WYSIWYG, we had issues in the toolbars they produced. The sets would be one long chunk, unable to wrap if required. So, we rebuilt the set into small chunks to fix that. Thankfully, that is no longer required. Now we often face a new challenge, getting ckeditor to allow more complex html. So our normal usage has changed to:

[php]
/**
* Implements hook_wysiwyg_editor_settings_alter().
*/
function custom_wysiwyg_editor_settings_alter(&$settings, $context) {
  if ($context[‘profile’]->editor == ‘ckeditor’) {
    $settings[‘allowedContent’] = TRUE;
  }
}
[/php]

5b. hook_views_api

https://api.drupal.org/api/views/views.api.php/function/hook_views_api/7

A simple, but required hook. We tend to use views api for one of two reasons most often:

  1. Seo (5c below)
  2. Storing views in versions controlled code, and deploying view updates by just clearing the cache.
  3. Special query altering magic

5c. hook_views_pre_render

http://api.drupal.org/api/views/docs%21views.api.php/function/hook_views_pre_render/7

Views can produce some pretty gnarly urls. To say it’s not search engine optimized is a drastic understatement. It gets especially tricky when trying to control those urls and the content on them. We begin altering the results based on geographic location, time of day, or anything else. A long time ago, we decided it just isn’t worth the fight to try and optimize all the permutations we can produce with views, so we hit it with the canonical hammer using hook_views_pre_render:
[php]
/**
* Implementation of hook_views_pre_render()
*/
function custom_views_pre_render($view){
 // Setup meta data for the view pages
 if( $view->display_handler->plugin_name == ‘page’ ){
   switch($view->name){
     default:
       // Canonical link back to their page
       $element = array(
         ‘#tag’ => ‘link’, // The #tag is the html tag –          ‘#attributes’ => array( // Set up an array of attributes inside the tag
           ‘href’ => url( $view->display_handler->get_url() ),
           ‘rel’ => ‘canonical’,
         ),
       );
       drupal_add_html_head($element, ‘canonical’);
   }
 }
}
[/php]

5d. hook_theme

https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_theme/7

Theming in Drupal is easy. Yet, many people seem to think it’s some sort of nightmare, because they ignore the actual theme system and just concatenate strings all day. The absolute easiest way to keep your code well organized is to make use of the two part theme system.

  1. Output html goes into template.tpl.php files.
  2. Logic required to prepare variables goes into preprocess functions.

If you just keep things that simple, you’ll find you can provide very minimal arguments to the theme, allowing the preprocess to isolate your heavy lifting, keeping frontend cleaner.

5e. hook_node_insert

There’s not a lot of great advice I can offer you about this one. It simply gives you the ability to save additional data about a node, or control the system in response.

A few of the more interesting hooks

Having covered my top 5, and their five way ties, let’s look instead at the things I use far more rarely. These are often fringe cases, specific to an application, but you might adapt it to your own solutions.

hook_menu_alter

https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_menu_alter/7

This hook is a godsend. If you want to keep your core safe to update over time, but have to have customization of a certain feature, this is your solution. You can take over control of an entire page.
Step 1, identify the page callback.
Step 2, copy the page callback’s logic as it stands into your custom module.
Step 3, use menu_alter to update the item to your own version.
In the example below, I take over the registration module’s pages so I can build a negative RSVP system on top:
[php]
/**
* Sub-implemenation of hook_menu_alter
* @param array $items
* @see registration_menu
*/
function _registration_menu_alter(&$items){
 $module_path = drupal_get_path(‘module’, ‘custom’) . ‘/registration’;
 // entity local tasks
 foreach (registration_get_registration_instances() as $instance) {
    $type = $instance[‘entity_type’];
    if (!in_array($type, array(‘registration’, ‘registration_type’))) {
      $items[$type . ‘/%entity_object/register’] = array(
        ‘load arguments’ => array($type),
        ‘title’ => ‘Register’,
        ‘page callback’ => ‘_custom_registration_register_page’,
        ‘page arguments’ => array(0, 1),
        ‘access callback’ => ‘registration_register_page_access’,
        ‘access arguments’ => array(0, 1),
        ‘type’ => MENU_LOCAL_TASK,
        ‘file path’ => $module_path,
        ‘file’ => ‘registration.pages.inc’,
      );
    }
 }
}
[/php]

hook_flush_caches

https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_flush_caches/7

This one requires a little more consideration. There’s no nice copy and paste example that would be useful for you. I’m more hoping to inspire an idea in you. If you have the ability to flush custom caches, it follows you might need them for ‘something.’ As you can see, 50% of my projects employ this, so there’s a good chance you’ll think of something you could pre-render, and serve from cache. This is the kind of performance work we usually start when we know users will be logged in, as the global page cache is disabled. One great example is to preprocess the entire html of a complex search result, then dish that out through views for the next 24 hours. Another handy idea is use this to watch for cache clears, and take action. Some sights cache clears can seem pretty mysterious at first, this can help you debug_backtrace the issue. Other sites might benefit from knowing the last time settings or caches were cleared for use in API calls.

hook_drush_command

http://www.sitepoint.com/drupal-create-drush-command/

If you’re working on Drupal, and aren’t familiar with Drush yet, you’re doing it wrong. This little system utility exposes loads of fun features to the command line allowing to perform bulk operations, clear caches, enable and disable modules, and any custom functionality you can imagine. Most often, we use for running content imports to avoid memory and timeout issues. I’ve also used it to expose a command for processing additional Drupal queue items more aggressively, while keeping cron runs periodic.

hook_services_resources_alter

http://www.drupalcontrib.org/api/drupal/contributions!services!docs!services.alter.api.php/function/hook_services_resources_alter/7

If you’re running services to get an API, it’s extremely unlikely you’ll be want all of the services to behave in the standard fashion. More often business needs specify specific validation and reactions to at least nodes, perhaps users being saved. This little guy will give you want you need to make that happen:
[php]
/**
* Allows alteration of the services_resources array.
*
* @param array $resources
*   The combined array of resource definitions from hook_services_resources.
* @param array $endpoint
*   An array describing the endpoint that resources are being built for.
*/
function _custom_services_resources_alter(&$resources, &$endpoint) {
  $resource_file_path = ‘custom.resources’;
 
  $resources[‘node’][‘operations’][‘retrieve’] = array(
    ‘file’ => array(‘type’ => ‘inc’, ‘module’ => ‘custom’, ‘name’ => $resource_file_path),
    ‘callback’ => ‘_custom_node_resource_retrieve’,
    ‘access file’ => $resources[‘node’][‘operations’][‘retrieve’][‘file’],
  ) + $resources[‘node’][‘operations’][‘retrieve’];
 
  $resources[‘node’][‘operations’][‘update’] = array(
    ‘file’ => array(‘type’ => ‘inc’, ‘module’ => ‘custom’, ‘name’ => $resource_file_path),
    ‘callback’ => ‘_custom_node_resource_update’,
    ‘access file’ => $resources[‘node’][‘operations’][‘retrieve’][‘file’],
  ) + $resources[‘node’][‘operations’][‘update’];
}
[/php]

hook_services_request_preprocess_alter

http://www.drupalcontrib.org/api/drupal/contributions!services!docs!services.alter.api.php/function/hook_services_request_preprocess_alter/7

This one is my own little pet project, and your fair warning. The Drupal services API is not so easy as ‘retrieve and resave’. The save functions run through the form_api, which expects the data appear in just a slightly different format. Using hook_services_request_preprocess_alter, I’ve managed to alter the incoming data to what the form_api expects, allowing you to simply resave. It’s still an open issue on drupal.org, and the full details of the work around are on my code blog.

hook_exit

https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_exit/7

The exit hook is a one of those you rarely think of. The only time you really need it, is when you want take action on cached pages. Using hook_exit, you can update maintenance or tracking tables, without getting a full bootstrap. Be aware, Drupal at lower bootstrap levels may not have all the toys you expect, and it will be too late for you to modify the cached page.

[more]

hook_views_query_alter

https://api.drupal.org/api/views/views.api.php/function/hook_views_query_alter/7

And for the last hook, let’s look at a little query magic so you can get an idea of what’s possible. Take this example:

Our site features users that are in various organic groups (og module).
We have events that may be public or open to only specific groups.
We have a view that is meant to find the match between some standard-ish filters, and the audience to user groups cross-over.

To make that happen, we setup the view’s filters this way:
Now we have to deal with that nasty little regex at the bottom. We’re going to change it to a list of audience matching the user’s groups using query alter. While we’re at it, let’s make sure admins see everything!

[php]
/**
* Implementation of hook_views_query_alter
* @see https://api.drupal.org/api/views/views.api.php/function/hook_views_query_alter/7
*/
function _events_views_query_alter__upcoming_events(&$view, &$query) {
 if( user_is_anonymous() ){
   // Simplify the query processing by just unsetting the other condition
   unset($query->where[2][‘conditions’][‘1’]);
 } elseif( user_access(‘administer nodes’) || user_access(‘edit any event’) ) {
   // Allow administrators to see all events regardless
   unset($query->where[2]);
 } else {
    // Restrict other users to only their groups
    $member_group_ids = og_get_groups_by_user();
    if( empty($member_group_ids) ){
      unset($query->where[2][‘conditions’][‘1’]);
    } else {
      // Use the built in second condition to create enough ors to cover the user’s groups
      $base_condtion = $query->where[2][‘conditions’][‘1’];
      unset($query->where[2][‘conditions’][1]);

     $group_nids = !empty($member_group_ids[‘node’])? array_keys($member_group_ids[‘node’]) : array(0);
     foreach($group_nids as $nid){
       $condition = $base_condtion;
       $condition[‘value’] = $nid;
       $query->where[2][‘conditions’][] = $condition;
     }
   }
 }
}
[/php]

Fin

That’s all folks. Not to say I haven’t learned and done far more in 5 years, the rest just wouldn’t interest to you. It’s all business, sparkle, and madness specific to each project after you really dig into why you’d use each hook.

I’d love to hear from some of you. What hooks are your top 5? Which ones did you need that one time to solve some crazy fringe use case? Which hooks are you using that aren’t even documented!? Your own don’t count!

[more]


 

Let's Get Started