Lambda Functions, Closures, and __invoke

Posted on April 26th, 2009 by Sam

Previously, lambda-style functions were only possible by using create_function. This has one major disadvantage, the function is compiled at run time, opcode caches can’t cache the function at compile time.

<?php
 
$func = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
echo "New anonymous function: $func\n";
echo $newfunc(2, M_E) . "\n";
// New anonymous function: lambda_1
// ln(2) + ln(2.718281828459) = 1.6931471805599
 
?>

As of PHP 5.3 Closure is now a reserved class, usage will create an Error while processing. The Closure class is used for internal implementation of anonymous functions. Closure evaluates its own environment and may have bound variables that can be accessed when the function is called.

<?php
 
echo preg_replace_callback('~-([a-z])~', function ($a) {
    return strtoupper($a[1]);
}, 'hello-world');
// helloWorld
 
?>

There are misconceptions on what closures may achieve. One suggestion is to use closures in order to create additional methods at run time. This is not the goal of closures and that functionality can already be achieved without closures, by using __call. When writing a closure, the semicolon is necessary, if left out it will produce a compile error.

Closures provide a new type of variable that may be called dynamically, this avails the implementation of __invoke. If defined, the object itself is callable, like a function itself, the new magic method will be invoked.

<?php
 
class foo {
    public function __invoke ($a) {
        echo strtolower($a);
    }
}
$bar = new foo;
$bar('Hello World!');
// hello world!
 
?>

Using closures for functions that accept a callback function is one of the obvious uses. Closures are useful in any situation where encapsulating logic in its own scope is acceptable. You can use it to simplify and make code more readable when refactoring old code.

<?php
 
$dbH = DatabaseHandler(HOST, USER, PASS);
EventLogger::event('test', 'DatabaseHandler', 'Create db connection');
$dbH->insert('People', array('name' => 'John Smith', 'age' => '28'));
EventLogger::event('test', 'DatabaseHandler', 'Insert John Smith (28)');
$dbH->insert('People', array('name' => 'Paul Michaels', 'age' => '43'));
EventLogger::event('test', 'DatabaseHandler', 'Insert Paul Michaels (43)');
$dbH->insert('People', array('name' => 'Frank Franklin', 'age' => '25'));
EventLogger::event('test', 'DatabaseHandler', 'Insert Frank Franklin (25)');
$dbH->insert('People', array('name' => 'Tony Shaw', 'age' => '67'));
EventLogger::event('test', 'DatabaseHandler', 'Insert Tony Shaw (67)');
 
?>

There’s a lot of repeating going on; that doesn’t have to be. Each call to EventLogger::event is identical except for one parameter. The calls to $dbH->insert have a similar composition also. We can put both calls into a closure and feed in the values.

<?php
 
$insertLogger = function ($name, $age) {
    $dbH->insert('People', array('name' => $name, 'age' => $age));
    EventLogger::event('test', 'DatabaseHandler', 'Inserting ' . $name . " ($age)");
};
 
$dbH = DatabaseHandler(HOST, USER, PASS);
EventLogger::event('test', 'DatabaseHandler', 'Create db connection');
$insertLogger('John Smith', 28);
$insertLogger('Paul Michaels', 43);
$insertLogger('Frank Franklin', 25);
$insertLogger('Tony Shaw', 67);
 
?>

We have made the code cleaner and more readable and in the event that a change is require, there is only one location to change. This provides a more efficient approach to coding, allowing for maintainable code.

Leave a Reply