A closure, as explained in the corresponding wikipedia article, is a computer language feature that allows a function defined within another function to use all the variables in the function that contains it. The typical usage of a closure is to create a lambda (anonymous) function that is used by an algorithmic function over a collection. In other words, this is what would be nice (but impossible right now):
$sum = 0;
array_walk($array, function ($x) { $sum += $x; });
While this may not seem as something very interesting in this specific case (after all, the version based on for_each is equivalent), it does get more interesting once you start to work on more complex data types that are not fully described by arrays or hash maps. Consider a form class with automatic validation of its fields:
$db = connect_to_database();
$labels = get_i18n_resource();
$form -> AddField('mail', 'input', function ($x) {
if (mail_exists($db, $x)) return i18n($labels, 'invalid mail');
return null;
});
Doesn’t PHP provide closures?
No, it does not. However, some features do come close. The most obvious is the create_function function from the standard library, which is available since PHP 4. It allows the creation of an anonymous function with the provided arguments and code:
$new = array_map($old, create_function('$x', 'return $x * 2;'));
However, the created function does not capture its environment. While it’s possible to access a global variable, or even to hardcode a constant into the body of the function, referencing local variables of the creator is impossible.
It should be noted that several workarounds have already been provided by clever authors. This one provides a way to extract the environment variables directly into a piece of code (it does not, however, include the possibility to pass arguments). This one uses a trick based on replacing positional parameters of the form $_1, $_2 … $_n.
Last but not least, PHP 5.3 has true closure support, although the local context variables have to be imported into the closure manually (more information here).
The solution
Since I don’t expect the excellent solution from PHP 5.3 to be widely supported for another while (my current hosting doesn’t even go beyond PHP 4, for what it’s worth), I want to propose my own solution which mixes the idiomatic syntax of create_function with the principle of importing values from the context manually. Another constraint is that the code should be clean and small. The expected usage is:
$sum = 0;
array_walk($array, create_closure('$x', '$sum += $x;', array( 'sum' => &$sum)));
That is, the function behaves like create_function, but takes a third argument that binds variable names in the code expressed as a second argument. The variables are bound to the provided values by reference, allowing modification of the context in addition to simple reading.
The implementation I use involves a global variable (which can be accessed from PHP4 anonymous lambdas) to provide an anonymous function with the context (by means of a prelude that loads the data from the globally available context into the appropriate variable names). The code is as follows:
function create_closure($args, $code, $context) { global $create_closure_data; $nth = count($create_closure_data); $create_closure_data[] = & $context; $prefix = 'global $create_closure_data;'; foreach ($context as $var => $ignored) $prefix .= sprintf('$%s = & $create_closure_data[%d][\'%s\'];', $var, $nth, $var); return create_function($args, $prefix . $code); }
Additional checks (mostly, testing each variable name to check that it’s a valid variable name) are fairly easy to add if required. This code is also hereby placed in the public domain, where allowed by law.
Hi. I'm Victor Nicollet,
Recent Comments