Anonymous functions in PHP

Anonymous functions are common in various modern languages, Ruby and Javascript being the popular one. But until version 5.3 PHP lacked true anonymous functions. Although newbie programmers are hard-pressed to find a suitable application for anonymous functions, they are indispensable if you do a lot of OOP, and can provide some elegant solutions to some particular problems.

PHP variable functions

Before we learn something about anonymous functions we will take a quick look into a PHP concept known as a variable function. It means that if we append parenthesis to a variable, then php will look for a function with the same name as to whatever the variable evaluates to and tries to execute it. Say we have the following simple function:

function Hello($name)
{
    echo "Hello $name";
}

We could then call the function indirectly by using a variable whose value evaluates to the function name. This can be quite useful when the name of the function that you want to execute cannot be determined till run-time.

$func = "Hello";
$func("World!");
 
//Output-
//Hello World!

Another example using a class and a static method:

<?php
 
class CHello
{
    static function hello($name)
    {
        echo "Hello $name";
    }
}
$func = "Hello";
CHello::$func("World!");
 
// Output-
// Hello World!
?>

Anonymous or lambda functions

There are times when you need to create a small localized throw-away function consisting of a few lines for a specific purpose, such as a callback. It is unwise to pollute the global namespace with these kind of single use functions. For such an event you can create anonymous or lambda functions using create_function. Anonymous functions allow the creation of functions which have no specified name. An example is shown below:

<?php
 
$str = "hello world!";
$lambda = create_function('$match', 'return "friend!";');
$str = preg_replace_callback('/world/', $lambda, $str);
echo $str ;
 
// Output
// hello friend!
?>

Here we have created a small nameless (Anonymous) function which is used as a callback in the preg_replace_callback function. Although create_function lets you create anonymous functions, it is not truly a part of the language itself but a hack. PHP 5.3 introduced true Anonymous functions in the base language itself. We create a unnamed function and assign it to a variable, including whatever parameters the functions accepts and then simply use the variable like an actual function. A example is given below:

<?php
 
$func = function($name)
{
    echo "Hello $name\n";
};
 
$func("world!");
$func("Sameer!")
 
//Output-
//Hello world!
//Hello Sameer!
?>

Note the ending semicolon at the end of the defined function. This is because the function definition is actually a statement, and statements always ends with a semicolon. Another example is shown below.

<?php
 
$str = "Hello World!";
$func = function($match)
{
    return "friend!";
};
 
$str = preg_replace_callback('/World/', $func, $str);
echo $str ;
 
// Output
// Hello friend!
?>

Anonymous and Nested functions

PHP allows functions to be nested inside one another. Although it seems like a side-effect of the parser rather then a design decision, it can be quite helpful in some situations. Take the example below. The function censorString takes a string as a parameter and replaces any censored word given with a string of ‘*’. The censorString functions defines a nested function replace that is used as a callback function by preg_replace_callback. Assuming that the ‘replace’ function is only used by the censorString function in our program it is better to define it within censorString itself and avoid polluting the global namespace with small single use functions

<?php
 
function censorString($string, $censor)
{
    function replace($match)
    {
        return str_repeat("*", strlen($match[0]));
    }
 
    return preg_replace_callback('/'.$censor.'/', 'replace', $string);
 
}
 
echo censorString("hello world!", "world");
echo censorString("hello world!", "hello");
 
// Output-
// hello *****!
// Fatal error: Cannot redeclare replace() 
?>

When you define a nested function as above, the inner function does not come into existence until the parent function is executed. Once the parent function (censorString) is executed the inner function (replace) goes into global scope. Now you can access the inner function from anywhere in your current document. One problem though is that calling the parent function again in the current document will cause a redeclaration of the inner function, which will generate an error, as the inner function is already declared. A solution is to use a anonymous function as shown below. (Note again the semicolon at the end of the inner function.)

<?php
 
function censorString($string, $censor)
{
    $func = function($match)
    {
        return str_repeat("*", strlen($match[0]));
    };
 
    return preg_replace_callback('/'.$censor.'/', $func, $string);
 
}
 
echo censorString("hello world!", "world");
echo censorString("hello world!", "hello");
 
// Output-
// hello *****!
// ***** world!
 
?>

Now whenever the censorString function is executed the inner anonymous function comes into existence. But unlike a normal nested function it goes out of scope once the parent function ends. So we can repeatedly call the censorString function without throwing a redeclaration error.

Another way is to define the function in the callback itself.

<?php
 
function censorString($string, $censor)
{
 
    return preg_replace_callback('/'.$censor.'/', 
                                function($match) 
                                {
                                    return str_repeat("*", 
                                           strlen($match[0]));
                                },
                                $string);
 
}
 
echo censorString("hello world!", "world");
echo censorString("hello world!", "hello");
 
// Output-
// hello *****!
// ***** world!
 
?>

Closures

Closures are anonymous functions that are aware of their surrounding context. In short these are anonymous functions which have knowledge about variables not defined within themselves. A simple example will make it clear. Say we want to create a anonymous function that returns a given number multiplied by 5.

<?php
 
$mult = function($x)
{
    return $x * 5;
};
 
echo $mult(2);
 
// Output-
// 10
?>

If we want to return a number multiplied by 7 rather then 5 ,we have to create another function and so on for other numbers. Instead of creating a series of different functions we can create a closure using the ‘use’ construct, which allows variables outside the anonymous function to be accessible or ‘closed’ within the current function.

<?php
 
$multiply = function($multiplier)
{
    return function($x) use ($multiplier)
    {
        return $x * $multiplier;
    };
};
 
// $mul5 now contains a function that returns a number multiplied by 5
$mult5 = $multiply(5);
 
// $mul7 contains a function that returns a number multiplied by 7
$mult7 = $multiply(7);
 
echo $mult5(5);
echo $mult7(5);
 
// Output-
// 25
// 35
 
?>

Take another example along the above lines. Lets say we want to filter an array of number according to a certain criteria; say all the numbers above 100. The code for the same is given below. Note the use of a anonymous function.

<?php
 
function filter($condition, $numbers) 
{
    $len = count($numbers);
    $filtered = array();
 
    /* Iterate through all the array elements */
    for($i = 0; $i < $len; $i++) 
    {
        $num = $numbers[$i];
 
        /* If the number satisfies the $condition, store
           it in the $filtered array
        */
        if($condition($num)) {
            $filtered[] = $num;
        }
    }
    return $filtered;
}
 
/* An array of random numbers */
$randomNumbers = array(34, 56, 22, 1, 5, 67, 897, 123, 4, 55);
 
$condition = function($x) 
{ 
    return ($x > 100) ? true : false; 
};
 
$greaterThan100 = filter($condition, $randomNumbers);
 
 
print_r($greaterThan100);
 
// Output
// Array ( [0] => 897 [1] => 123 ) 
?>

Now what if we want to allow all numbers above 400, then we have to change the anonymous function to the following.

 
$condition = function($x) 
{ 
    return ($x > 400) ? true : false; 
};

Rather then creating different functions for various criteria, we can create a closure.

 
function filter($condition, $numbers) 
{
    $len = count($numbers);
    $filtered = array();
 
    /* Iterate through all the array elements */
    for($i = 0; $i < $len; $i++) 
    {
        $num = $numbers[$i];
 
        /* If the number satisfies the $condition, store
           it in the $filtered array
        */
        if($condition($num)) {
            $filtered[] = $num;
        }
    }
    return $filtered;
}
 
 
/* createFilter now returns a anonymous function */
function createFilter($lowerBound)
{
    return function($x) use ($lowerBound)
    {
        return ($x > $lowerBound) ? true : false;
    };
}
 
 
/* An array of random numbers */
$randomNumbers = array(34, 56, 22, 1, 5, 67, 897, 123, 4, 55);
 
/* Create a new function and store it in $greaterThan400 */
$greaterThan400 = createFilter(400);
$greaterThan100 = createFilter(100);
 
print_r(filter($greaterThan400, $randomNumbers));
print_r(filter($greaterThan100, $randomNumbers));
 
// Output
// Array ( [0] => 897 ) 
// Array ( [0] => 897 [1] => 123 )

Note that in the above example when createFilter exists, normally the $lowerBound variable goes out of scope, but because we have used closure here using the ‘use’ keyword, the inner anonymous function binds the $lowerBound variable with itself even after the createFilter function exists. This is what we call closure. The inner function ‘closes’ over the variables of the outer function in which it is defined.

We can do a var_dump on the $greaterThan400 and $greaterThan100 objects to see if the inner function really carries the $lowerBound variable with itself.

var_dump($greaterThan400);
var_dump($greaterThan100);

Which returns the following:

 
object(Closure)#1 (2) {
  ["static"]=>
  array(1) {
    ["lowerBound"]=>
    int(400)
  }
  ["parameter"]=>
  array(1) {
    ["$x"]=>
    string(10) "<required>"
  }
}
 
object(Closure)#2 (2) {
  ["static"]=>
  array(1) {
    ["lowerBound"]=>
    int(100)
  }
  ["parameter"]=>
  array(1) {
    ["$x"]=>
    string(10) "<required>"
  }
}

Or better yet we can use the Reflection API.

echo ReflectionFunction::export($greaterThan400);

Which gives the following:

Closure [ <user> function {closure} ] {
  @@ D:\localhost\test\\index.php 27 - 30
 
  - Bound Variables [1] {
      Variable #0 [ $lowerBound ]
  }
 
  - Parameters [1] {
    Parameter #0 [ <required> $x ]
  }
}

In closing

Lambda functions and closures have taken PHP a notch closer towards other modern languages. In practice how much people really use lambdas and closures in their daily work remains to be seen. I’m still getting a hang of it.

11 thoughts on “Anonymous functions in PHP

  1. At first blush, I don’t really like this idea. These examples really seem like a great way to make your code hard to understand. The closures and nested anonymous functions are just going to leave maintenance developers left scratching their heads. Is all this really worth the trouble?

    I’d love to see a more in-depth useful example scenario. Maybe that would open my eyes a bit more so I could see the benefit of these new capabilities.

  2. I didn’t say lambda functions were easier to understand at a first look. These concepts come from a functional paradigm (Lisp, Scheme, Haskell) which most people find hard to grasp because of years of training in the imperative languages (Java, C, C++, Pascal, PHP). I’ll try to post some long examples in a coming post.

  3. The ability to create anonymous functions existed for some time (as you mentioned in the first part) via create_function. I never used it and thought it was more or less really ugly as it is the same story with eval.

    What is more interesting for me is the memory consumption & execution time as this was a major problem in previous PHP versions. Do you have any news with the release of PHP 5.3 and real closure/lambda support (and no hacking anymore)

Comments are closed.