Problems with module_invoke due to changes in PHP 3.3 call by reference.

I've been working on a site built in Drupal 6.  The site is to integrate with an existing system and therefore I had to integrate into an existing login authentication mechanism.  The components are:

  • Existing front-end login/logout mechanism, written in PHP.
  • Existing back-end scripts, written in Perl.
  • New back-end areas, written in Drupal, ultimately in PHP.

I have therefore written my own authentication module based on the code example at this web site and also from the book Pro Drupal Development 2nd Edition.

Amongst other things, I had my own login validation function, along the lines of:

function mymodule_login_validate( $form, &$form_state )
{
  $username = $form_state['values']['name'];

  // next function checks whether the user exists in the existing 3rd party database          
  if ( externalUserExists( $username )) {
     // If they do, we authenticate against them                              
     if ( externalUserValidPassword( $username, $form_state['values']['pass'] )) {              
          user_external_login_register( $username, 'mymodule' );
          user_authenticate_finalize( $form_state['values'] );
        }  
       // else drop through to the end and return nothing
       // Drupal will handle the rejection for us                     
 } else {
      // there's no user in the 3rd party database, 
      // so we authenticate against the Drupal DB: this gives us the ability                            
      // to continue to have admin users                                                                            
      user_login_authenticate_validate( $form, &$form_state );
    }
}

It was all working well on my Debian 5 Lenny server, until I upgraded the server to Debian 6 Squeeze: at this point the login just stopped working, it just didn't happen!

 
After much scratching of bonce and investigating, I found the problem to be within Drupal's module_invoke() function (module.inc).  I was using it from within the existing login code thus:
module_invoke('mymodule','login_validate', null, &$formFields);
Looking at the code of module invoke and around the web, I discovered that in the process of moving from Debian 5 to 6, PHP had gone from version 5.2.7 to version 5.3.3, a rather big leap.  Then I spotted that on the release of 5.3.0 a few things had changed.  One of which was to do with the two PHP functions func_get_args and call_user_func_array, used in Drupal's module_invoke function and that this was mentioned as causing an issue in module_invoke on the Drupal web site.
 
The code of module_invoke() is:
 
function module_invoke() {
  $args = func_get_args();
  $module = $args[0];
  $hook = $args[1];
  unset($args[0], $args[1]);
  $function = $module . '_' . $hook;
  if (module_hook($module, $hook)) {
    return call_user_func_array($function, $args);
  }
}
 
In essence the problem seems that if module_invoke() is passed any arguments that are references (variables beginning with &$ in PHP) these are lost and therefore not passed through to the function called using call_user_func_array().  This is because func_get_args(), which is used to fetch them, dereferences them.  In other words, it changes the variable from a reference to a normal variable.
 
So in my code, when I pass &$formFields to module_invoke(), func_get_args() changes it to simply $formFields, which is then passed to mymodule_login_validate() by call_user_func_array() as a non-referenced variable, but the former is expecting a reference, so it doesn't work!
 
Something like that.
 
Anyway, I came up with the workaround of hacking some code in to module_invoke():
   $result = call_user_func_array($function, $args);
    if ( ($module == 'mymodule') && (!$result) ) {
      $result = mymodule_login_validate(null, $args[3]);             
    }
    return $result;
But that's a real hack.  Looking at the Drupal thread, it seems like there's no real adopted solution to fix this issue at the moment, even in the latest development version 8 of Drupal! 
 
I then realised I was being stupid and didn't need to call module_invoke() at all, because my module was fully available to me in the login script's namespace, because I had loaded all the Drupal environment: All I had to do was to call my function directly with:
 mymodule_login_validate(null, &$formFields);
Doh!  Solved, and obviously an unnecessary investigation and a bit of a waste of time on my part, but hopefully the story might be useful to someone out there...