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: