Taxonomy Colour: Drupal Module Development Tutorial (Part One)

24th May 2010

I recently developed a simple module for a project I was working on, which is so generic that it may well be useful to other people or on other projects. I was surprised to find that there was no existing module (that I could find) doing the same thing, so I wrote it myself (I'd actually inherited the project, and the person working on it had tried to achieve the same thing but by hacking part of the Drupal core - which is not a smart idea). The structure and implementation of the module was such that it seemed an ideal example to use as a tutorial in Drupal module development.

For those not familiar with the Taxonomy module, this core piece of Drupal functionality (though not enabled by default) allows you to categorise content in a number of ways - the most obvious being simple categories or tags.

The brief for this module was simple - an item of content (an event, in this case) was associated with a single category belonging to a particular vocabulary. For rendering menus and calendars, each category needed to be colour-coded - thus, I needed a way to associate a colour with a category.  

What We'll Cover

What makes this module a good example to use as a tutorial is that it covers a comprehensive range of areas. The module, and therefore this tutorial, will cover:

  • The basics of Drupal module development
  • The anatomy of a module
  • Creating installation profiles to modify the database
  • Interacting with the database
  • Creating a module configuration page
  • Using variables
  • Hooks
  • Using hook_form_alter to modify an existing form
  • Views integration
  • Caching

Getting Started

When I'm developing a module, I prefer to start with a clean, "blank" install of Drupal - in fact I have a sandboxed installation running on my machine purely for development.  It's such a quick and easy installation process that it's really no hassle to do so, particularly once you start playing around with time-saving mechanisms such as installation profiles, features and the invaluable Drush.

That said, you may need to develop the module within an existing site - that's fine, so long as you clone that site or use an existing development version.  The excellent Backup and Migrate module is an invaluable tool for doing this.

Anatomy of a Drupal module

Ok, now you've got Drupal running, you'll need to create the directory structure and the first two files that make up a module.  

Your module should be a new directory in /sites/all/modules.  If the modules directory doesn't already exist (it doesn't in a clean installation), create it now. Please don't be tempted to create your module in the top-level modules directory. This contains core Drupal modules, and will be overwritten whenever you upgrade Drupal (something you should be doing as required!).

A simple module can, as a minimum, comprise just two files. The .info file tells Drupal's autodiscovery mechanism about the module and is therefore essential, as well as certain other information such as the version and any dependencies on other modules.

The implementation of the module itself goes in a file with the extension .module. This is just a PHP file - if you're using an IDE (Integrated Development Environment) it might be worth setting it up so that it recognises a .module file as PHP, so you can take advantage of features such as code completion and syntax checking.

Both files have the same name, different extensions, and this name is also the name of your directory. Stick to lowercase, use only underscores, and try and keep it unique - it's always worth checking the Drupal project pages to see if someone has already developed a module with the same name. You'll be using this prefix a lot - by prefixing your function names with this string you're keeping them unique, as well as adhering to certain requirements when it comes to hooks - but more on this later.

I'm going to call the module taxonomy_color. So, create the directory /sites/all/modules/taxonomy_color, and in that directory two new files: taxonomy_color.info and taxonomy_color.module.

The .info File

As I said, the info file tells Drupal about your module.

name = Taxonomy Color
description = Allows colours to be assigned to taxonomy terms
package = Other
dependencies[] = taxonomy
version = VERSION
core = 6.x

Name and description will be obvious. The package is simply a logical grouping, and dictates where it appears when you go to the module listing page (admin/build/modules/list).

Dependencies references modules required for your module to operate correctly, and enabling your module without those modules being available and enabled will throw an error or request that the administrator enable them before this can be used. Because this module operates directly in conjunction with Drupal's core Taxonomy module, I've added this line - note that, as previously mentioned - the Taxonomy module is not enabled by default. Note that the "name" of the module here is basically the name of the directory. This module sits in /modules rather than /sites/all/modules, since it's part of the core.

The core directive indicates that this module is designed for the Drupal 6 branch. There are significant differences in the way modules are implemented between versions, so this is important to note.

The Installation File

We're going to use an installation file to set up our new table programmatically, using the drupal_install_schema() function within an implementation of hook_install(). When the module is enabled, this table is created for us and conversely, should the module be uninstalled then the process is reversed, with the table being dropped via an implementation of hook_uninstall() that in turn calls drupal_uninstall_schema().  (Note that simply disabling the module will not cause this to be run - it has to be uninstalled. It's common to disable modules in certain situations - such as when a module is being upgraded - when the last thing you want is to lose your data as well!)

The other task we need to carry out is to create default values for the module settings - however, since when we fetch a variable from the database we can specify a default value to use should it not have been set, the .install file is not necessarily the best place to do this.

Should a future version of a module require changes to the schema, using install files is the correct way to tell Drupal how to handle this.  This is outside the scope of this tutorial, so I refer you to the documentation.

Here is the initial version of the install file (we're going to add an additional table later for caching - but we'll come to that):

/**
 * Implementation of hook_install().
 */
function taxonomy_color_install() {
    $result = drupal_install_schema('taxonomy_color');

    if (count($result) > 0) {
        drupal_set_message(t('The Taxonomy Color module was installed.'));
    }
    else {
     drupal_set_message(t('Taxonomy Color table creation failed. Please "uninstall" the module and retry.'));
    }
}

Nothing too complicated here - we use an implementation of hook_schema to return an array-based representation of the new table, which in turn is installed - or uninstalled - in the manner described above.

Now let's look at the function that defines the table in question:

/**
 * Implementation of hook_schema().
 */
function taxonomy_color_schema() {
  $schema['term_color'] = array(
    'module' => 'Taxonomy Color',
    'description' => t('Mapping of term to color.'),
    'fields' => array(
      'tid' => array(
        'description' => t('Term identifier.'),
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'disp-width' => '10',
      ),
      'color' => array(
        'description' => t('Hex color code.'),
        'type' => 'varchar',
        'length' => '6',
        'not null' => TRUE),
      ),
    'primary key' => array('tid'),
  );

  return $schema;
}

So, when you install the module, you should find - have a look using phpMyAdmin or similar - that you have a new table specifically for this module. Finally, we add an instruction that tells Drupal to remove this table when the module is uninstalled;

/**
 * Implementation of hook_uninstall().
 */
function taxonomy_color_uninstall() {
  drupal_uninstall_schema('taxonomy_color');  
}

I say finally - let's just throw in another table, which we'll use later; we'll do so by modifying our implementation of hook_schema() above.

/**
 * Implementation of hook_schema().
 */
function taxonomy_color_schema() {
    $schema['term_color'] = array(
    'module' => 'Taxonomy Color',
    'description' => t('Mapping of term to color.'),
    'fields' => array(
      'tid' => array(
        'description' => t('Term identifier.'),
        'type' => 'int',
        'unsigned' => TRUE,
        'not null' => TRUE,
        'disp-width' => '10',
        ),
      'color' => array(
        'description' => t('Hex color code.'),
        'type' => 'varchar',
        'length' => '6',
        'not null' => TRUE),
      ),
    'primary key' => array('tid'),
    );

  $schema['cache_tax_color'] = array(
    'module' => 'Taxonomy Color',
    'fields' => array(
            'cid' => array(
        'description' => t('Primary Key: Unique cache ID.'),
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
      'default' => ''),
    'data' => array(
      'description' => t('A collection of data to cache.'),
      'type' => 'blob',
      'not null' => FALSE,
      'size' => 'big'),
    'expire' => array(
      'description' => t('A Unix timestamp indicating when the cache entry should expire, or 0 for never.'),
      'type' => 'int',
      'not null' => TRUE,
      'default' => 0),
    'created' => array(
      'description' => t('A Unix timestamp indicating when the cache entry was created.'),
      'type' => 'int',
      'not null' => TRUE,
      'default' => 0),
    'headers' => array(
      'description' => t('Any custom HTTP headers to be added to cached data.'),
      'type' => 'text',
      'not null' => FALSE),
    'serialized' => array(
      'description' => t('A flag to indicate whether content is serialized (1) or not (0).'),
      'type' => 'int',
      'size' => 'small',
      'not null' => TRUE,
      'default' => 0)
    ),
  'indexes' => array('expire' => array('expire')),
  'primary key' => array('cid'),
  );

  return $schema;
}

In the next part of this tutorial, we'll get started on the module itself, starting with interacting with the database.