Always Check for variable_get('install_task') == 'done' in hook_cron() and hook_init() Implementations

March 11th, 2012 Permalink

Something that I've noticed of late is that a large contingent of otherwise sterling Drupal module developers have been slow to come to terms with the existence of custom installation profiles. A number of big Drupal projects use them, so it is good for module developers to pay attention and ensure that their modules work when installed as a part of a profile. What this means in practice is that there are a few areas in which failing to account for the situations under which hooks may be invoked will produce a module that can break any installation profile that includes it. For example:

1) hook_requirements()

hook_requirements() is called when a module is installed, which can happen either after Drupal is installed, or during the install process when the module is included in a custom installation profile. In the former case, the hook implementation doesn't have to return an array. In the latter case, an implementation that fails to return an array will break the entire profile installation process. If you head out there to look around, I'm sure that you'll find a few other fairly widely used modules that have this flaw.

That's a nice gotcha buried in the Drupal core, but there are plenty of modules - again fairly widely used - that simply fail to abide by the documentation for hook_requirements() and try to do things in their hook_requirements() implementation that will fail during install of a custom installation profile that includes the module.

2) hook_cron()

hook_cron() implementations will be invoked prior to the tail-end completion of an installation process. This is usually fine, but if you have any doubts about whether or not your module should be participating at that point, then it's a good idea to put in a check:

/**
 * Implements hook_cron().
 */
function my_module_cron() {
  // don't run unless the install is complete
  if(variable_get('install_task') != 'done') {
    return;
  }

Another point where this can trip up developers is in the way in which the Simpletest module sets up a new test case. If you look at DrupalWebTestCase::setUp(), you'll see that it is essentially running a new installation process to create the schema and environment for testing. This, naturally, includes running cron:

protected function setUp() {

  ...

  // Run cron once in that environment, as install.php does at the end of
  // the installation process.
  drupal_cron_run();

  ...

  // Restore necessary variables.
  variable_set('install_task', 'done');

  ...

}

So when you are building the test cases for your own code, alpha-testing your cron implementation and its test harness, it will make your life simpler if you just return from your hook_cron() implementation when the install_task variable is not set to 'done'. That way your implementation will only be called from your test code, rather than also in the setUp() method.

3) hook_init()

hook_init() is a very large and blunt tool with which to bludgeon a Drupal installation. It will be called on every page load, AJAX or otherwise, and regardless of any other considerations. During an installation, for example, the modules specified as dependencies in the installation profile .info file will be installed in batches via AJAX calls. As soon as a module with a hook_init() implementation is installed in one of those AJAX calls, that implementation will be executed on every further AJAX call, and on all remaining page loads in the installation process. For the right sort of module and custom installation profile, this can cause havoc - crashes, intermittent failures of installation, strange PDO database errors, you name it. But even for more innocuous uses of hook_init(), I think that in general you shouldn't be running your hook_init() implementation prior to the completion of the installation process. So put in a check:
/**
 * Implements hook_init().
 */
function my_module_init() {
  // don't run unless the install is complete
  if(variable_get('install_task') != 'done') {
    return;
  }