Press "Enter" to skip to content

PHP Internals — Anonymous Functions

In PHP, the way we pass callbacks around has always been ugly. Before PHP 5.3, we had only two choices:

1. A function name as a string
2. The return value of create_function

After PHP 5.3, we got one more choice, namely Closure,

$func = function () { ... };
array_walk($arr, $func);

In terms of implementation, the first approach — passing a function name string — is the simplest.
The second approach, create_function, is essentially the same as the first: create_function returns a function name as a string, and that name has the format:

"\000_lambda_" . count(anonymous_functions)++;

Let's look at the implementation steps of create_function:

1. Fetch the arguments and the function body
2. Assemble a string of the form "function __lambda_func (args) { body;}"
3. eval it
4. Find the function body produced by eval in the function table via __lambda_func; raise an error if not found
5. Define a function name: "00_lambda_" . count(anonymous_functions)++
6. Replace __lambda_func with the new function name
7. Return the new function name

Let's verify that:

<?php
create_function("", 'echo __FUNCTION__;');
call_user_func("\000lambda_1", 1);
?>
//output
__lambda_func

Because at eval time the function name is "__lambda_func", the anonymous function prints __lambda_func from within. And since the function table entry "__lambda_func" is finally renamed to "00_lambda_" . count(anonymous_functions)++, you can invoke this anonymous function through "00_lambda_" . count(anonymous_functions)++.
To confirm this, you can dump the return value of create_function and take a look.
When PHP 5.3 was released, one of its new features was support for closures / Lambda Functions. My first reaction was to assume that zval had gained a new IS_FUNCTION, but in fact it constructs an instance of the Closure "class" introduced in PHP 5.3. The Closure class has a private constructor, so it cannot be instantiated directly; it is also a Final class, so it cannot serve as a base class for derived subclasses.

//php-5.3.0
$class = new ReflectionClass("Closure");
var_dump($class->isInternal());
var_dump($class->isAbstract() );
var_dump($class->isFinal());
var_dump($class->isInterface());
//output:
bool(true)
bool(false)
bool(true)
bool(false)
?>

And PHP 5.3's support for closures does no more than store the external variables to be bound as the "Static properties" of the Closure object (not properties in the ordinary iterable/accessible sense).

//php-5.3.0
$b = "laruence";
$func = function($a) use($b) {};
var_dump($func);
/* output:
object(Closure)#1 (2) {
  ["static"]=>
  array(1) {
    ["b"]=>
    string(8) "laruence"
  }
  ["parameter"]=>
  array(1) {
    ["$a"]=>
    string(10) "<required>"
  }
}
*/

Personally, I think this implementation is still rather too crude compared with JS's support for closures.

Be First to Comment

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.