Dev Blog

From some time, PHP offers closures called also anonymous functions. The anonymous function works somewhat different than JavaScript equivalent. In JavaScript all variables are available in closure if they are defined in same scope as closure. Let's make an example in JavaScript, so that will be a bit cleaner:

// Variable $name is *outer* for $greet function
var $name = 'Joe';
var $greet = function() {
    console.log($name); // $name is `Joe` here.
}

In PHP however, these outer variables are not automatically available in function. In fact these variables are copied (with exception to objects), just like when passing variable as function argument. They need to be passed with use keyword. Equivalent in PHP:

// Variable $name is *outer* for $greet function
$name = 'Joe';
$greet = function() use ($name) {
    echo $name; // $name is `Joe` here.
}

Isolating Variables Scope

Often anonymous functions are used just to isolate scope of contained variables. That is, to create kind of private local variables. Such function is actually executed just after it was created. The plus is that it does not define any new symbols, so that it will not only prevent name collisions with other code, but itself does not add any symbol name. In JavaScript it can be defined by surrounding function with parenthesis and calling such construct. Also optional parameters can be provided, for instance window to pass variables to outer scope:

(
    function(window, name){
        console.log('Hello ' + name);
        window.test = 'foo';
    }
)(window, 'Joe');
console.log(window.test);

After executing such function, window object will have new test property, and the code inside function body will be executed. Without polluting symbol namespace.

PHP offers anonymous function too, and it turned out that those functions can also be executed without assigning them to any variable. The syntax is actually the same as in JavaScript:

<?php
$window = new stdClass;
(function($window, $test){
    echo 'Hello ' . $test . PHP_EOL;
    $window->test = 'foo';
})($window, 'Joe');
echo $window->test . PHP_EOL;

Use Case

1. Using private variables in globally included file

There might be some use cases for such construct especially in application bootstrap files or autoloaded file, which will handle something. In example below, the anonymous function is used to define class aliases. This allows including such file anywhere in application without concerns of overwriting existing variables: 

<?php
(function(){
    $classes = [
        'Maslosoft\\Widgets\\Breadcrumbs' => 'Breadcrumbs',
        'Maslosoft\\Widgets\\Html\\Decorator' => 'Decorator',
        'Maslosoft\\Widgets\\Search\\SearchDrawer' => 'SearchDrawer',
    ];
    foreach ($classes as $fullName => $shortName) {
        class_alias($fullName, $shortName);
    }
})();

Such file can be included in composer.json file autoload section, so that all class aliases will be available in application, whether dependency containing this snippet will be installed:

{
  "autoload": {
    "psr-4":{
      "files": [
        "src/Aliases.php"
      ]
    }
  }
}

2. Isolating code fragments to increase robustness

I've ran into copy-paste issue while loading CSV data from several sources in a quick'n'dirty simple script. The issue was, that I accidentally used result from previous assignement. As the scope was not isolated, the code worked properly, however yield unexpected results. Because of large amount of similar data, I couldn't figure out what's wrong.

Consider following code, where two CSV files are loaded into hash maps, however accidentally and result of $hPoints was assigned instead of $vPoints.

// H Src points
$reader = new CsvReader(getcwd() . '/H_Points.csv');
$hPoints = [];

foreach ($reader->read() as $pointData)
{
        $hPoints[$pointData['number']] = $pointData;
}

h::set($hPoints, 'h-points');


// V Src points
$reader = new CsvReader(getcwd() . '/V_Points.csv');
$vPoints = [];

foreach ($reader->read() as $pointData)
{
        $vPoints[$pointData['number']] = $pointData;
}

h::set($hPoints, 'v-points');

There were any warning, nor even IDE highlighted the issue. Now, using scope isolation, the variable $hPoint would be not available in scope of $vPoints.

Here is the example with isolated scope, my PHPStorm instantly highlights issue with the red line:

(function(){
        // H Src points
        $reader = new CsvReader(getcwd() . '/H_Points.csv');
        $hPoints = [];

        foreach ($reader->read() as $pointData)
        {
                $hPoints[$pointData['number']] = $pointData;
        }

        h::set($hPoints, 'h-points');
})();

(function(){
        // V Src points
        $reader = new CsvReader(getcwd() . '/V_Points.csv');
        $vPoints = [];

        foreach ($reader->read() as $pointData)
        {
                $vPoints[$pointData['number']] = $pointData;
        }

        h::set($hPoints, 'v-points');
})();

And the result in IDE, where issue was instantly obvious. Compare the two cases: