Early on in PHP’s life, its type system contained only strings, ints, reals, arrays, “resources,” and null. A number of features have been bodged in on top of this minimal system without really extending it. “Function pointer” functionality was one of the first.
The initial implementation simply took a string and looked it up in the interpeter’s global function table, then invoked it:
1 2 3 4 5 6 7 8 9 | <?php function add($x, $y) { return $x + $y; } $f = "add"; print $f(1, 2); /* Prints 3 */ ?> |
This probably got implemented in a couple of hours while Rasmus or one of his minions debated how it should work on IRC, and it shows:
- Because a “function” variable is any arbitrary string, there’s no way to statically validate them. Static analysis of PHP is a crap-shoot anyways, so this isn’t a big loss on its own, but overloading one type (
string) to do something magic introduces more chances for programmer confusion, too. - Attempting to call an undefined function is a fatal error, as it should be. However, since it’s trivial to create a “function” variable that refers to an undefined function, this is a lot more headache-inducing than it needs to be. The presence of
function_existsdoesn’t help; it just makes generic code that much more verbose. - There are multiple code paths that result in a string being evaluated as a function name, and they don’t all have the same semantics.
call_user_funcdoesn’t abort your script if you pass it a bogus name, but you can’t tell if the called function might returnFALSE, you’ll never know it becausecall_user_funcreturns that value to indicate errors. - Several early examples of how to use this feature showcased scripts that accepted function names straight out of user input, which is a security disaster on par with arbitrary code injection. Just because you only created HTML fields for certain names doesn’t mean malicious or bored users won’t start stuffing garbage in there.
Unfortunately, when PHP 4 introduced a simple object model to PHP (necessitating a new variable type, for objects — but more on that later), the PHP team opted not to clean up this hole in the language. Instead, support for methods and static methods was bolted on top. Instead, the $variable() syntax was extended to accept arrays as well. All of the following variables might be callable:
- Function names as strings.
$f = "add";- Instance methods, as an array.
$ops = new MathOperations(); $f = array($ops, "add");- Static methods, as an array.
$f = array("StaticMathOperations", "add");- Static methods, as a string.
$f = "StaticMathOperations::add";
There’s no real way of knowing if a value is callable unless you try.
PHP 5.3’s closure syntax addresses some of this: closures are handled as a resource type within the PHP interpreter, finally. However, they come with their own weird implementation limitations.