Taxonomy Colour: Drupal Module Development Tutorial (Part One)

24th May 2010 | Tags:

I recently devel­oped a sim­ple mod­ule for a project I was work­ing on, which is so generic that it may well be use­ful to other peo­ple or on other projects. I was sur­prised to find that there was no exist­ing mod­ule (that I could find) doing the same thing, so I wrote it myself (I’d actu­ally inher­ited the project, and the per­son work­ing on it had tried to achieve the same thing but by hack­ing part of the Dru­pal core — which is not a smart idea). The struc­ture and imple­men­ta­tion of the mod­ule was such that it seemed an ideal exam­ple to use as a tuto­r­ial in Dru­pal mod­ule development.

For those not famil­iar with the Tax­on­omy mod­ule, this core piece of Dru­pal func­tion­al­ity (though not enabled by default) allows you to cat­e­gorise con­tent in a num­ber of ways — the most obvi­ous being sim­ple cat­e­gories or tags.

The brief for this mod­ule was sim­ple — an item of con­tent (an event, in this case) was asso­ci­ated with a sin­gle cat­e­gory belong­ing to a par­tic­u­lar vocab­u­lary. For ren­der­ing menus and cal­en­dars, each cat­e­gory needed to be colour-​coded — thus, I needed a way to asso­ciate a colour with a category.

What We’ll Cover

What makes this mod­ule a good exam­ple to use as a tuto­r­ial is that it cov­ers a com­pre­hen­sive range of areas. The mod­ule, and there­fore this tuto­r­ial, will cover:

  • The basics of Dru­pal mod­ule development
  • The anatomy of a module
  • Cre­at­ing instal­la­tion pro­files to mod­ify the database
  • Inter­act­ing with the database
  • Cre­at­ing a mod­ule con­fig­u­ra­tion page
  • Using vari­ables
  • Hooks
  • Using hook_​form_​alter to mod­ify an exist­ing form
  • Views inte­gra­tion
  • Caching

Get­ting Started

When I’m devel­op­ing a mod­ule, I pre­fer to start with a clean, “blank” install of Dru­pal — in fact I have a sand­boxed instal­la­tion run­ning on my machine purely for devel­op­ment. It’s such a quick and easy instal­la­tion process that it’s really no has­sle to do so, par­tic­u­larly once you start play­ing around with time-​saving mech­a­nisms such as instal­la­tion pro­files, fea­tures and the invalu­able Drush.

That said, you may need to develop the mod­ule within an exist­ing site — that’s fine, so long as you clone that site or use an exist­ing devel­op­ment ver­sion. The excel­lent Backup and Migrate mod­ule is an invalu­able tool for doing this.

Anatomy of a Dru­pal module

Ok, now you’ve got Dru­pal run­ning, you’ll need to cre­ate the direc­tory struc­ture and the first two files that make up a module.

Your mod­ule should be a new direc­tory in /sites/all/modules. If the mod­ules direc­tory doesn’t already exist (it doesn’t in a clean instal­la­tion), cre­ate it now. Please don’t be tempted to cre­ate your mod­ule in the top-​level mod­ules direc­tory. This con­tains core Dru­pal mod­ules, and will be over­writ­ten when­ever you upgrade Dru­pal (some­thing you should be doing as required!).

A sim­ple mod­ule can, as a min­i­mum, com­prise just two files. The .info file tells Drupal’s autodis­cov­ery mech­a­nism about the mod­ule and is there­fore essen­tial, as well as cer­tain other infor­ma­tion such as the ver­sion and any depen­den­cies on other modules.

The imple­men­ta­tion of the mod­ule itself goes in a file with the exten­sion .module. This is just a PHP file — if you’re using an IDE (Inte­grated Devel­op­ment Envi­ron­ment) it might be worth set­ting it up so that it recog­nises a .module file as PHP, so you can take advan­tage of fea­tures such as code com­ple­tion and syn­tax checking.

Both files have the same name, dif­fer­ent exten­sions, and this name is also the name of your direc­tory. Stick to low­er­case, use only under­scores, and try and keep it unique — it’s always worth check­ing the Dru­pal project pages to see if some­one has already devel­oped a mod­ule with the same name. You’ll be using this pre­fix a lot — by pre­fix­ing your func­tion names with this string you’re keep­ing them unique, as well as adher­ing to cer­tain require­ments when it comes to hooks — but more on this later.

I’m going to call the mod­ule taxonomy_color. So, cre­ate the direc­tory /sites/all/modules/taxonomy_color, and in that direc­tory two new files: taxonomy_color.info and taxonomy_color.module.

The .info File

As I said, the info file tells Dru­pal 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 descrip­tion will be obvi­ous. The pack­age is sim­ply a log­i­cal group­ing, and dic­tates where it appears when you go to the mod­ule list­ing page (admin/​build/​modules/​list).

Depen­den­cies ref­er­ences mod­ules required for your mod­ule to oper­ate cor­rectly, and enabling your mod­ule with­out those mod­ules being avail­able and enabled will throw an error or request that the admin­is­tra­tor enable them before this can be used. Because this mod­ule oper­ates directly in con­junc­tion with Drupal’s core Tax­on­omy mod­ule, I’ve added this line — note that, as pre­vi­ously men­tioned — the Tax­on­omy mod­ule is not enabled by default. Note that the “name” of the mod­ule here is basi­cally the name of the direc­tory. This mod­ule sits in /modules rather than /sites/all/modules, since it’s part of the core.

The core direc­tive indi­cates that this mod­ule is designed for the Dru­pal 6 branch. There are sig­nif­i­cant dif­fer­ences in the way mod­ules are imple­mented between ver­sions, so this is impor­tant to note.

The Instal­la­tion File

We’re going to use an instal­la­tion file to set up our new table pro­gram­mat­i­cally, using the drupal_install_schema() func­tion within an imple­men­ta­tion of hook_install(). When the mod­ule is enabled, this table is cre­ated for us and con­versely, should the mod­ule be unin­stalled then the process is reversed, with the table being dropped via an imple­men­ta­tion of hook_uninstall() that in turn calls drupal_uninstall_schema(). (Note that sim­ply dis­abling the mod­ule will not cause this to be run — it has to be unin­stalled. It’s com­mon to dis­able mod­ules in cer­tain sit­u­a­tions — such as when a mod­ule 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 cre­ate default val­ues for the mod­ule set­tings — how­ever, since when we fetch a vari­able from the data­base we can spec­ify a default value to use should it not have been set, the .install file is not nec­es­sar­ily the best place to do this.

Should a future ver­sion of a mod­ule require changes to the schema, using install files is the cor­rect way to tell Dru­pal how to han­dle this. This is out­side the scope of this tuto­r­ial, so I refer you to the doc­u­men­ta­tion.

Here is the ini­tial ver­sion of the install file (we’re going to add an addi­tional 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.'));
    }
}

Noth­ing too com­pli­cated here — we use an imple­men­ta­tion of hook_schema to return an array-​based rep­re­sen­ta­tion of the new table, which in turn is installed — or unin­stalled — in the man­ner described above.

Now let’s look at the func­tion 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 mod­ule, you should find — have a look using php­MyAd­min or sim­i­lar — that you have a new table specif­i­cally for this mod­ule. Finally, we add an instruc­tion that tells Dru­pal to remove this table when the mod­ule 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 mod­i­fy­ing our imple­men­ta­tion 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 tuto­r­ial, we’ll get started on the mod­ule itself, start­ing with inter­act­ing with the database.

Comments

    So? where is your second part?

    13th May 2011
    Anonymous
    Anonymous

    Paid work got in the way, but well reminded - I’ve started writing it tonight. If you message me or @ me on Twitter, I’ll let you know when it’s published.

    17th May 2011
    Lukas White
    Lukas White

    Second part is missing why?

    1st July 2011
    Anonymous
    Anonymous

Links and images are allowed, but please note that rel="nofollow" will be automactically appended to any links.