PHP Short Functions and Scope

Yesterday I took an interest in PHP internals looking into @enunomaduro‘s RFC for Auto-capturing multi-statement closures.

(Have I said before that I loved studying compilers in CS?)

This lead me down some interesting rabbit holes and I wanted to make some notes on what I found.

Short vs Long closures

There are a two forms of “closure” in PHP.

Long closures have the form:

$a = [1, 2, 3];

$addToArray = function ($item) use ($a) {
  array_push($a, $item);
  var_dump($a); // outputs 1, 2, 3, 4
};

$addToArray(4);

Short closures have the form:

$a = [1, 2, 3];

$addToArray = fn($item) => var_dump(array_merge($a, [$item])); // outputs 1,2,3,4

$addToArray(4);

Note that short closures (aka “arrow functions”):

  • use fn() instead of function()
  • “auto-capture” the enclosing scope: you don’t need to include the use()
  • can only execute a single expression, and this is what is returned from the closure (which I why I had to wrangle the array_merge into there)

The proposed RFC (which seems to make a lot of sense to me) would allows a multi-line, auto-capturing closure with the syntax:

$a = [1, 2, 3];

$addToArray = fn ($item) {
  array_push($a, $item);
  var_dump($a); // outputs 1, 2, 3, 4
};

$addToArray(4);

All the different functions and their conventions

There are three different things going on in the different kinds of function definition:

  1. Anonymous vs named functions

A named function has a name and must be declared before use:

function addToArray() { ... }

addToArray(...);

Whereas an anonymous function has no name. This means that it can be assigned to a variable, as I have done above:

$addToArray = function ($item) use ($a) { ... };

$addToArray(...);

or it can be used “inline”

array_map( function ($item) use($var) { ... }, $someArray );

2. Auto-capturing vs non-auto-capturing

The short fn() syntax is “auto-capturing”. This means you don’t need to use use() to import variables from the outer scope.

The longer function() syntax is not “auto-capturing”. You need to manually import variables from the outer scope using use()

3. Statement vs expression

The statement version is the “multi-line” version of a function definition. You use the curly braces to define the statement that the function executes. This may or may not return a value.

The expression is the shorter “single-line” version (though the single expression may actually span multiple lines) and always returns the value of the expression.

These three syntax forms allow up to eight different types of function:

  • Anonymous, auto-capturing statement (which Nuno is proposing)
  • Anonymous, non-auto-capturing, statement (which exists)
  • Names, non-auto-capturing, statement (which most people would consider a ‘regular’ PHP function)
  • etc.

This is all described in the “Background” section of the RFC along with reasons why some of them may/should never exist.

Understanding the conventions of fn() vs function(), and expression vs statement is really helpful in understanding PHP code and what type of function to write when.

ONE MORE THING: Scope!

BUT… the biggest thing that I learned about PHP closures – that is all anonymous functions regardless of auto- or non-auto-capturing – is that the imported variables from the outer scope are imported as VALUES, not REFERENCES… That is, if you change the value inside the function, the outer scope is not affected:

$a = 2;
$fn = function() use ($a) {
  $a = $a + 1;
}
var_dump($a); // outputs 2
$fn();
var_dump($a); // outputs 2

You can see that at work in this live snippet.

AND!!… the function captures that value at the time the function is declared not the time the function is called:

$a = 2;
$fn = function() use ($a) {
  var_dump($a); // outputs 2
};
$a++;
var_dump($a); // outputs 3
$fn();

You can see that at work in this live snippet.

This is all VERY different to how closures work in JavaScript and definitely worth knowing if, like me, you’ve done functional-style programming in JS and want to switch to a similar style in PHP.

So… I learned a lot diving into this little RFC. It was fun! I hope you learned something too.

Useful reference: Short Closures in PHP on the Stitcher blog.