Better Control Over Drupal Modules Running Order

If you've been a Drupal developer for a while you know that modules run according to the value of the weight field in the system table. You can update this table using an install file so your module runs exactly when you need it. This is what community tags does to run after tagadelic and the method I've used to make sure some form_alter code runs after everything else when I needed to modify event forms.

But the change in the module's weight affect all the hooks in it. What happens if you want to control the runnning order in a per hook basis? I needed to do this to run some nodeapi operations in a custom module before event_nodeapi and form_alter stuff, in the same module, after event_form_alter. Believe me, you may need this some time (unless you opt for using before and after modules).

This idea inspired me to code a bit and individually override the operations in event_nodeapi with some code in my custom module. It worked nice. Now I can run operations load and view in event_nodeapi and override insert and update with code from my module.

The concept is quite simple. I added an event_nodeapi_overriders table with two fields: module and op. My module just needs to use its install file to insert values in this table. If I want mymodule to provide overriders for insert and update operations in event_nodeapi I should add the following module/op pairs to the table: mymodule/insert and mymodule/update.

Then I added a few select boxes to show the values from event_nodeapi_overriders table in the event settings page where I can choose which module should override which operation and used some conditions with variable_get() in event_nodeapi and mymodule_nodeapi to decide which code to run.

Perhaps the concept could be extended to eventually have a matrix of all hooks in all modules or a hook_weight parameter to allow developers to decide the running order. Or maybe is too much overhead?

Anyway, this was some kind of experiment and even if after talking to Gerhard we decided to update the event module to use Form API in the submission process instead, the idea of having better control over hooks running order could deserve some more thinking.

Join the conversation

Nice idea. You have

Nice idea. You have mentioned that you actually wrote that code, but I can't find it.
Any directions?

Some code for nodeapi overriding

These are a few ideas of my code. In mymodule.module:

function _mymodule_implode_date($date, $hour, $minute, $ampm) {
if (variable_get('event_ampm', '0') && $ampm == 'p') {
$hour += 12;
}
$datetime= $date . ' ' . str_pad($hour, 2, '0', STR_PAD_LEFT) . ':' . str_pad($minute, 2, '0', STR_PAD_LEFT);
return $datetime;
}

function mymodule_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
// if event enabled node
if(variable_get('event_nodeapi_'. $node->type, 'never') != 'never') {
// remove spaces from $op
$op_override = str_replace(' ', '', $op);

switch ($op) {
case 'load':
// run override for event_nodeapi
if (variable_get('event_nodeapi_' . $op_override,'none') == 'mymodule') {
// code to override load operation
}
break;
case 'validate':
// run override for event_nodeapi
if (variable_get('event_nodeapi_' . $op_override,'none') == 'mymodule') {
$form_values = $_POST;
$node->event_start = _mymodule_implode_date($node->event_start, $node->start_time_hour, $node->start_time_minute, $form_values['start_time_ampm']);
$node->event_end = _mymodule_implode_date($node->event_end, $node->end_time_hour, $node->end_time_minute, $form_values['end_time_ampm']);
if (event_is_later($node->event_start, $node->event_end, 'string')) {
$node->event_end = $node->event_start;
}
}
break;

In mymodule.install:

function mymodule_install() {
// Drop module weight so we act after event and eventrepeat
$weight = (int)db_result(db_query("SELECT weight FROM {system} WHERE name = 'eventrepeat'"));
db_query("UPDATE {system} SET weight = %d WHERE name = 'st49events'", $weight + 1);
db_query("INSERT INTO {event_nodeapi_overriders} (module, op) VALUES ('%s', '%s')", 'st49events', 'delete');
db_query("INSERT INTO {event_nodeapi_overriders} (module, op) VALUES ('%s', '%s')", 'st49events', 'delete revision');
db_query("INSERT INTO {event_nodeapi_overriders} (module, op) VALUES ('%s', '%s')", 'st49events', 'insert');
db_query("INSERT INTO {event_nodeapi_overriders} (module, op) VALUES ('%s', '%s')", 'st49events', 'load');
db_query("INSERT INTO {event_nodeapi_overriders} (module, op) VALUES ('%s', '%s')", 'st49events', 'prepare');
db_query("INSERT INTO {event_nodeapi_overriders} (module, op) VALUES ('%s', '%s')", 'st49events', 'search result');
db_query("INSERT INTO {event_nodeapi_overriders} (module, op) VALUES ('%s', '%s')", 'st49events', 'print');
db_query("INSERT INTO {event_nodeapi_overriders} (module, op) VALUES ('%s', '%s')", 'st49events', 'update');
db_query("INSERT INTO {event_nodeapi_overriders} (module, op) VALUES ('%s', '%s')", 'st49events', 'submit');
db_query("INSERT INTO {event_nodeapi_overriders} (module, op) VALUES ('%s', '%s')", 'st49events', 'update index');
db_query("INSERT INTO {event_nodeapi_overriders} (module, op) VALUES ('%s', '%s')", 'st49events', 'validate');
db_query("INSERT INTO {event_nodeapi_overriders} (module, op) VALUES ('%s', '%s')", 'st49events', 'view');
db_query("INSERT INTO {event_nodeapi_overriders} (module, op) VALUES ('%s', '%s')", 'st49events', 'alter');
db_query("INSERT INTO {event_nodeapi_overriders} (module, op) VALUES ('%s', '%s')", 'st49events', 'rss item');
}

This is how the event_nodeapi_overriders should be created from event.install, or any other part in your code if you're using this method for other modules:

db_query("CREATE TABLE {event_nodeapi_overriders} (
module varchar(64) NOT NULL default '',
op varchar(64) NOT NULL default ''
) /*!40100 DEFAULT CHARACTER SET utf8 */;");

And the original module you want to override should use something like this, in my example the functions modified are event_admin_overview_settings and event_nodeapi. These are some of the select boxes for the settings page:

$form['event_nodeapi_overriders'] = array(
'#type' => 'fieldset',
'#title' => t('Nodeapi Overriders'),
);

$form['event_nodeapi_overriders']['event_nodeapi_delete'] = array(
'#type' => 'select',
'#title' => t('Delete'),
'#default_value' => variable_get('event_nodeapi_delete','none'),
'#options' => _event_nodeapi_overriders('delete'),
'#description' => t('Module that overrides delete case in event_nodeapi')
);

$form['event_nodeapi_overriders']['event_nodeapi_deleterevision'] = array(
'#type' => 'select',
'#title' => t('Delete Revision'),
'#default_value' => variable_get('event_nodeapi_deleterevision','none'),
'#options' => _event_nodeapi_overriders('delete revision'),
'#description' => t('Module that overrides delete revision case in event_nodeapi')
);

$form['event_nodeapi_overriders']['event_nodeapi_insert'] = array(
'#type' => 'select',
'#title' => t('Insert'),
'#default_value' => variable_get('event_nodeapi_insert','none'),
'#options' => _event_nodeapi_overriders('insert'),
'#description' => t('Module that overrides insert case in event_nodeapi')
);

$form['event_nodeapi_overriders']['event_nodeapi_load'] = array(
'#type' => 'select',
'#title' => t('Load'),
'#default_value' => variable_get('event_nodeapi_load','none'),
'#options' => _event_nodeapi_overriders('load'),
'#description' => t('Module that overrides load case in event_nodeapi')
);

This is a helper function:

function _event_nodeapi_overriders($op) {
$modules = array('none' => 'none');
$sql = "SELECT module FROM {event_nodeapi_overriders} WHERE op = '%s' ORDER BY module";
$result = db_query($sql, $op);
while ($overrider = db_fetch_object($result)) {
$modules[$overrider->module] = $overrider->module;
}
return $modules;
}

And finally code like this in event_nodeapi:

function event_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
// make sure it's an event enabled node
if(variable_get('event_nodeapi_'. $node->type, 'never') != 'never') {
$op_override = str_replace(' ', '', $op);
switch ($op) {
case 'load':
if (variable_get('event_nodeapi_' . $op_override,'none') == 'none') {
// run code here if no nodeapi override for this operation
}
break;
}

This is not code to be copied and pasted, it requires some more thought but you may get the idea from it.

Alexis Bellido

i enjoy your article. great

i enjoy your article. great job. keep in simple

Keep your comments relevant, written in good English and don't spam. Let's create useful and valuable discussions. Markdown is welcome.

Add your comment