Call by name and PHP

A little while ago, the question of allowing named parameteres in function calls was raised on the JSON-RPC mailing list.

As you might or might not know, named parameters (as opposed to positional parameters) are used by many database programming languages, and in some languages not really database related. The main advantage of using named parameters is that you can choose which parameters to pass to a function, omitting all the ones you do not need. PHP, with default parameter values, does something similar, with two small catches:

  • the parameters have to be passed to the function in the same order that they were declared (this is, imho, not a big problem in most situations)
  • given a function with 3 parameters, of which only the first is manadatory, the caller is not allowed to omit the parameter that occupies position 2 if he wants to specify the parameter at position 3. He can of course attain the same effect by specifyng the default value for parameter 2, but that means that he must know that value.

Named parameters are useful e.g. when a function takes a very long list of options, most of which are not compulsory.

The “php way” of doing this usually involves declaring a function as accepting a single parameter of type array, and passing all the options as key=>value pairs. The downside is that this forces the coder implementing the function to write quite a bit of code to validate all the options received.

By taking advantage of the introspection capabilties offered by PHP 5, it is possible to ease this burden. I have whipped up this code snippet that migth come handy in situations where a lot of call-by-name is used.

There are some caveats to take into account:

  • Despite the function working on php 5 only, it does not make use of exceptions, and relies on php 4 error mechanism instead
  • hell might freeze over when you try it on functions that accept parameters by reference (or return by ref)

Enjoy

/**
* Call a user function using named instead of positional parameters.
* If some of the named parameters are not present in the original function, they
* will be silently discarded.
* Does no special processing for call-by-ref functions...
* Cannot be used on internal PHP functions.
* @param string $function name of function to be called
* @param array $params named array with parameters used for function invocation
*/
function call_user_func_named($function, $params)
{
  // make sure we do not throw exception if function not found: raise error instead...
  // (oh boy, we do like php 4 better than 5, don't we...)
  if (!function_exists($function))
  {
    trigger_error('call to unexisting function '.$function, E_USER_ERROR);
    return NULL;
  }
  $reflect = new ReflectionFunction($function);
  if ($reflect->isInternal())
  {
    trigger_error('cannot call by name internal function '.$function, E_USER_ERROR);
    return NULL;
  }
  $real_params = array();
  foreach ($reflect->getParameters() as $i => $param)
  {
    $pname = $param->getName();
    if ($param->isPassedByReference())
    {
      /// @todo shall we raise some warning?
    }
    if (array_key_exists($pname, $params))
    {
      $real_params[] = $params[$pname];
    }
    else if ($param->isDefaultValueAvailable()) {
      $real_params[] = $param->getDefaultValue();
    }
    else
    {
      // missing required parameter: mark an error and exit
      //return new Exception('call to '.$function.' missing parameter nr. '.$i+1);
      trigger_error(sprintf('call to %s missing parameter nr. %d', $function, $i+1), E_USER_ERROR);
      return NULL;
    }
  }
  return call_user_func_array($function, $real_params);
}

Leave a Reply

Your email address will not be published. Required fields are marked *