Updated "enabled modules includes" snippet for Drupal 6 bootstrap modules
Updated "enabled modules includes" snippet for Drupal 6 bootstrap modulesSaturday, 16th Mar 2013
In a previous post I demonstrated some snippets for automatically including a set of .inc files based on currently enabled modules. This provides a nice way of "future proofing" your code as it means that as modules are enabled/disabled on a project, your code is automatically included on an "as needed" basis.
Recently I discovered that the provided snippet does not work for Drupal 6 inside bootstrap modules.
What is a bootstrap module?
Any module with the "bootstrap" field set to 1 in the "system" table in your database is a bootstrap module.
Assuming that Drupal is configured to fire hooks for cached pages (Aggressive caching disabled in Drupal 6) then bootstrap modules will have their hook_boot()
and hook_exit()
functions run during the Drupal bootstrap phase.
Most contrib and custom modules are not bootstrap modules as these two hooks can be a little difficult to work with and are only required for implementing rather specialised functionality.
There are two very good reasons not to implement hook_boot()
if you can avoid it:
- Most Drupal Core and all non-bootstrap modules have not included their functions yet, so you will be missing almost all API functionality and have to work with "raw" PHP for any functionality you're building.
- If Aggressive caching is disabled your module will be loaded and your code run even for cached pages. Generally it's a good idea to avoid placing this extra overhead on your server.
How do I create a bootstrap module?
If your module implements hook_boot()
at the time it is installed then Drupal will flag it as a bootstrap module. Subsequently removing hook_boot()
from your module does not trigger Drupal to remove the bootstrap module flag from the system table.
If this is a module you are developing the only ways to remove the bootstrap module flag is to update the system table yourself (eg. in hook_update_N()
) or to completely uninstall and reinstall the module (simply disabling and re-enabling the module is not enough).
How does this effect the way we're including our module.inc files?
Here's the snippet from my previous post:
/**
* Include additional files.
*/
foreach (module_list() as $module) {
if (file_exists($file = dirname(__FILE__) . "/includes/{$module}.inc")) {
require_once $file;
}
}
The problem is that this file is included during bootstrap if this is a bootstrap module. This means that the default behaviour of module_list()
(without passing any parameters) is to return only bootstrap modules, meaning that all of your module.inc files for modules that are non-bootstrap modules will be ignored.
One fix for this is to pass module_list() additional parameters to force it to return the full list of enabled modules, rather than just bootstrap modules.
According to the API documentation the first parameter is used to reset module_list()
's internal static cache, it's important we do this because the static cache of module_list()
behaves somewhat counter-intuitively. If the first parameter $refresh
is not TRUE
then all subsequent parameters are ignored and module_list()
simply returns the list that was generated the last time $refresh
was TRUE
(by default $refresh
is FALSE
).
So, to get a full module list in a bootstrap module we have to update our snippet to look more like this:
// As this is a bootstrap module we tell module_list() to refresh its cache and
// return more than just the bootstrap enabled modules so we can handle our .inc
// files.
$modules = module_list(TRUE, FALSE);
// As module_list() is statically cached we don't want to accidentally confuse
// other scripts running during the boot phase, so we call it again to set the
// internal cache back to what it was before we overwrote it.
// In D7 simply replace these two lines with system_list('module_enabled').
module_list(TRUE, TRUE);
foreach ($modules as $module) {
if (file_exists($file = dirname(__FILE__) . "/includes/{$module}.inc")) {
require_once $file;
}
}
Unfortunately, this is rather inefficient as we have to hit the database twice to get our module list but at least it works. In Drupal 7 we have the system_list()
function that can retrieve a full module list during bootstrap and (correctly) statically caches each generated list, rather than just the last generated list so we can entirely avoid the extra performance overhead of running module_list()
multiple times.
Ok, now I'm seeing fatal errors!
I've been using the above code in a few different places and I discovered that some contrib modules don't like what we're doing there - even when we're being careful to leave the internal state of module_list()
as we found it. For example, I encountered fatal errors for a single page load when using the Persistent Login module and dropping the sessions
table manually then logging back in with a registered user account. I'm not sure why this is the case but the following code works for me 100% of the time (but it's a lot uglier):
/**
* Returns a list of modules. Replaces module_list() during bootstrap.
*
* @todo Replace this with system_list() in D7.
*/
function MODULENAME_bootsafe_module_list() {
$modules = array();
$result = db_query("SELECT name, filename, throttle FROM {system} WHERE type = 'module' AND status = 1 ORDER BY weight ASC, filename ASC");
while ($module = db_fetch_object($result)) {
if (file_exists($module->filename)) {
// Determine the current throttle status and see if the module should be
// loaded based on server load. We have to directly access the throttle
// variables, since throttle.module may not be loaded yet.
$throttle = ($module->throttle && variable_get('throttle_level', 0) > 0);
if (!$throttle) {
drupal_get_filename('module', $module->name, $module->filename);
$modules[$module->name] = $module->name;
}
}
}
return $modules;
}
$modules = MODULENAME_bootsafe_module_list();
foreach ($modules as $module) {
if (file_exists($file = dirname(__FILE__) . "/includes/{$module}.inc")) {
require_once $file;
}
}
The reason this always works is because I copied and pasted the relevant code from module_list()
that is already run for bootstrap modules in core and created this "bootsafe" version from it.
Syndicate: planet drupal