PHP: call_user_func no good if you need to pass by reference

In PHP the call_user_func function is used to call a function whose name you won’t know until runtime.   In Dataface I need to do this sort of thing quite a bit because the developers are allowed to implement methods in their delegate classes that follow naming conventions. For example, they might define a method called firstname__permissions() that returns the permissions for the firstname field.   Since Dataface is written to be generic, I can’t very well hard code a call to the firstname__permissions() method inside the Dataface core.

This is where I turned to call_user_func so that I can do something like this:

// Suppose the field name we want to check was passed to us in the $fieldname variable.
$delegate =& $this->getDelegate(); // gets the delegate class
$permissions =& call_user_func( array(&$delegate, $fieldname."__permissions"), $this);

call_user_func will accept a function call as either a string function name (e.g. ‘trim’) or as a 2-element array, where the first element is an object, and the second element is a string which is the name of the method to call on that object. So call_user_func(array(&$delegate, $fieldname."__permissions")) is actually calling something like $delegate->firstname__permissions() (if the value of $fieldname was ‘firstname’).

Problem: Objects in PHP 4 are passed by value.

Now suppose our firstname__permissions() is defined like:
function firstname__permissions(&$record){ ... }. Note that the ‘&’ prepended to the $record parameter means that we want to pass the object by reference. This is necessary in PHP since objects are passed by value by default. There are many reasons why we don’t want to pass objects by value. For starters, if we make changes to the object inside our function those changes will be lost because they would have been applied to a copy of the object and not the object itself.

More problems: call_user_func passes everything by value

So you think you’ve solved your problem by defining firstname__permissions(&$record) to take the $record parameter by reference. WRONG! If we call it like:
call_user_func(array(&$delegage, 'firstname__permissions'), $record) the $record parameter will still be passed by value because call_user_func passes everything by value.

Solution: Don’t use call_user_func

One nice feature of PHP that most responsible PHP developers avoid like the plague is variable variables and variable functions. However this is one case where these come in handy. For instance, suppose we have the case above where we are calling call_user_func(array(&$delegate, $fieldname.'__permissions'), $record);. Call it this way will result in $record being passed by value.

However, we can use a variable method name as follows:

$methodname = $fieldname.'__permissions';
$out = $delegate->$methodname($record);

This solution will call our variable function AND pass $record by reference so everyone is happy.

But be responsible. Don’t over-use variable method calls.

comments powered by Disqus