OK, So I'm trying to develop a useful, general-purpose hook (to allow modules to modify a $term object whenever they want - testing Catch's Term Hooks patch on a D6 backport thing)
bangpound attempted this in a D6 bridging module last month at the EOL/taxonomy code sprint called taxidermy . I saw that that module needed to remake a module_invoke_all-alike to work as intended, and it seemed odd.

module_invoke_all() currently runs a bunch of operations on a given set of arguments - and returns an array of data which is usually glued into the original subject to add info to it.
OK. That's sorta useful, but not as useful as letting the invoked hooks add their data to the object themselves as needed.
I think.

So OK, we can just pass-by-reference and it'll work. It works well for hook_form_alter, doesn't it?
No. A few experiments with module_invoke_all and mods thereof prove that although call_user_func_array() does refs, we cannot use func_get_args() and still get the info back to the caller.

Hm. OK, so how does hook_form_alter() do it so well?

Oh Noes! hook_form_alter() (or actually drupal_alter()) has to do creepy things to make this seem to work. Namely - it passes a list of elements that have to be handled as handles as a value hidden inside the first parameter - ... or something like that. Anyway, it's voodoo. And documented as such.

I want:
- A hook
- That I can invoke, by passing in an object
- Have other modules play with that object
- And get it back changed
- Possibly with values modified or even removed from it.


//////////////////
// Experimental

/**
 * Run module_invoke_all and support pass-by-ref.
 * 
 * I can't find the magic way to do this intelligently. Brute it instead
 */
function module_invoke_all_by_ref($hook, &$arg1 = NULL, &$arg2 = NULL, &$arg3 = NULL, &$arg4 = NULL, &$arg5 = NULL) {
  foreach (module_implements($hook) as $module) {
    $function = $module .'_'. $hook;
    if (module_hook($module, $hook)) {
      call_user_func_array($function, array(&$arg1, &$arg2, &$arg3, &$arg4, &$arg5));
    }
  }
}

NOW - I know that brute-setting 5 magic-number args is not the most elegant code.
But IT WORKS.
and if you want more than 5 parameters passed by ref, you are madder than me.

The current approach used in drupal_alter to address this PHP problem is freaky and I don't really think this easily-understandable way is worse than the arcane __drupal_alter_by_ref hack.

I can copy & paste this magic inside my own place where it gets used - but I'm trying to solve the general case here. I've seen a bunch of modules which have to do their own thing because module_invoke_all isn't good enough. I'd guess that some modules that use module_implements do so inside an identical foreach loop, and do so because module_invoke_all() isn't good enough.

I'm not yet suggesting that this hack of mine should be tried in real code, but I am asking if any brighter PHP-minds out there can suggest a workable solution to my problem statement above.

Feedback? Suggestions?
.dan.

Comments

afreeman’s picture

... but ultimately I think this should be filed as a bug report. module_invoke_all() shouldn't be eating function arguments regardless of what you're trying to pass through it.

emanaton’s picture

Greetings All,

I currently banging out a D6 module that needs by-ref functionality in module_invoke_all, and am not seeing any better approach than this... am I missing anything?

I've expanded my implementation a bit to cover absolutely all scenarious (by bumping the byref args up to 10) and to more closely match the current interation of the production module_invoke_all() function:

function <modulename>_invoke_all_by_ref($hook, &$arg1 = null, &$arg2 = null, 
          &$arg3 = null, &$arg4 = null, &$arg5 = null, &$arg6 = null,  
          &$arg7 = null, &$arg8 = null, &$arg9 = null, &$arg10 = null 
  ) {
  $return = array();
  foreach (module_implements($hook) as $module) {
    $function = $module .'_'. $hook;
    $result = call_user_func_array($function, array(
      &$arg1, &$arg2, &$arg3, &$arg4, &$arg5, 
      &$arg6, &$arg7, &$arg8, &$arg9, &$arg10
    ));
    if (isset($result)) {
      if (is_array($result)) {
        $return = array_merge_recursive($return, $result);
      } else {
        $return[] = $result;
      }
    }
  }

  return $return;
}