You probably know about dependencies in modules and how to declare them in a module's .info file. In case you're not familiar with the concept, it looks like this:
dependencies[] = imagefield
dependencies[] = nodereference
dependencies[] = content_copy
dependencies[] = views
dependencies[] = imagecache(Slightly different in Drupal 5.x - be warned!)
Each one of those modules must be enabled before Drupal will allow you to enable the module you want to install. Neat, huh? But there is a problem:
If the depended-upon modules exist but are not yet enabled, Drupal offers to enable them for you. What it doesn't tell you is that it will enable those modules, and the original module you wished to install, in an arbitrary order. Either that or it enables them all at once in the same process. Either way, it will *not* enable all the dependencies and then enable the initial module separately, as you might expect and as would be truly "safe" behaviour.
This can cause problems if your hook_install() (the function containing code you wish to execute on installation of your module) contains calls to functions within those modules. Those functions will not be available, because the module is not yet loaded in. A case in point is my CCK Gallery module, which tries to drupal_execute() a form presented by the Content Copy module and calls functions within the ImageCache module. If they're not available, the installation fails in an ugly way, and if they weren't installed prior to CCK Gallery then they will *not* be available, even though Drupal tells you it will install them for you.
Note, I pitched in a Drupal 7.x feature request along these lines:
http://drupal.org/node/365098
The way around this for Drupal developers using Drupal 5.x and above is to have a hook_requirements() which can check for the existence of various things before installing and abort if they're not there:
http://api.drupal.org/api/function/hook_requirements
I'm not going to go in to massive detail, because you can RTFM like I had to, but here is my hook implementation for your reference. It successfully aborts installing CCK Gallery if the functions I need aren't available for any reason, thus getting around the ugly failure I was seeing:
<?php
/**
* Implementation of hook_requirements()
*/
function cck_gallery_requirements($phase) {
$requirements = array();
$t = get_t();
switch ($phase) {
case 'install':
$error = FALSE;
if (!module_exists('content_copy')) {
$error = TRUE;
$value = $t('Content Copy module to be pre-installed.');
$severity = REQUIREMENT_ERROR;
}
if (!module_exists('imagecache')) {
if ($error) {
$value .= $t(' ImageCache module to be pre-installed.');
} else {
$error = TRUE;
$value .= $t('ImageCache module to be pre-installed.');
}
$severity = REQUIREMENT_ERROR;
}
if ($error) {
$requirements['cck_gallery'] = array(
'title' => $t('CCK Gallery requirements'),
'value' => $value . $t(' If the required modules are now installed, please enable CCK Gallery again.'),
'severity' => $severity,
);
}
break;
}
return $requirements;
}
?>It seems any module calling functions in another module that isn't required core really *must* implement hook_requirements() in it's .install file.

2 comments
The descriptions need to be in the 'description' attribute
The error message description should be in the 'description' attribute instead of the 'value' attribute. Otherwise, the error message isn't formatted properly. Thanks for the example!
Automating as much as possible
I've got a situation where (at the moment) meeting the dependencies necessary to create content types is all I'm worried about, so I varied your approach by creating a separate function that grabs the dependency list for my module, and checks for any modules that depend on 'content'. That way I only need to specify the required modules in my .info file, and I can call this function on hook_update_N() if any new field modules are required in future.
<?php
/**
* Implementation of hook_requirements().
*/
function mymodule_requirements($phase) {
$requirements = array();
$t = get_t();
switch ($phase) {
case 'install':
$dependencies = _mymodule_check_dependencies();
if ($dependencies) {
$module_list = '<ul><li>'. implode('</li><li>', $dependencies) .'</li></ul>';
$requirements['mymodule'] = array(
'title' => $t('My Module requirements'),
'description' => $t('<p>The follwing modules need to be enabled and configured <em>before</em> My Module can be enabled:</p>') . $module_list,
'severity' => REQUIREMENT_ERROR,
);
}
break;
}
return $requirements;
}
/**
* Check for modules which need to be installed before hook_install() is invoked.
*/
function _mymodule_check_dependencies() {
$modules = module_rebuild_cache();
$dependencies = array();
foreach ($modules['mymodule']->info['dependencies'] as $dependency) {
// Anything we depend on which depends on 'content' is required by mymodule_install() to create content types.
if (in_array('content', $modules[$dependency]->info['dependencies']) && !module_exists($dependency)) {
$dependencies[] = $dependency;
}
}
return $dependencies;
}
?>
Post new comment