Camille Hodoul

Using PHP arrow functions

December 07, 2019

PHP 7.4 was released with support for arrow functions, as defined in this RFC.
To paraphrase it, the goal is to provide a concise syntax for closures performing simple tasks.
Basically, we’re avoiding many redundant use imports.

In the context of this RFC,
“simple task” means a single statement returning a value.

If you’re coming from the JavaScript world, be wary of a few key differences in semantics, including but not limited to:

  • variables in the outer scope are implicitly captured by-value. The implication is that if you mutate them in the closure, the outer variable won’t change.

    $outer = 1;
    $test = fn() => ++$outer;
    assertEquals(2, $test()); // true
    assertEquals(2, $outer);  // false
  • multi-statement bodies are not supported at this time. Note that you can still write multiline single-statements.

Despite those constraints, there are still many use cases.

Simple tasks: array_ functions

The fn() syntax is obviously very expressive for simple tasks such as mapping over, reducing or filtering an array:

$users = [
    ["id" => 123, "authorized" => true],
    ["id" => 234, "authorized" => false]
];

$getAuthorized = fn() =>
    array_filter(
        $users, 
        fn($user) => $user["authorized"]
    )
;
assertEquals(1, count($getAuthorized())); // true

The meaningful words count to boilerplate ratio is a lot better than with the old function() use() { return; } syntax.

Mutation in objects

$this binding in arrow functions works the same as in “normal” closures.
That’s one way you can affect the outside world other than returning a value.

class MyObj
{
    protected $count = 0;
    public function increment()
    {
        ++$this->count;
    }
    public function getCount()
    {
        return $this->count;
    }
    public function mapIncrement(array $values)
    {
        return array_map(fn() => $this->increment(), $values);
    }
}
$instance = new MyObj;
$instance->mapIncrement(["a", "b", "c"]);
assertEquals(3, $instance->getCount());

One-liners

The temptation (for me anyway) is to bend the “single-statement” rule by writing crazy one-liners, so that you can benefit from the implicit capture of the outer scope.

For instance this naive yet very unreadable curry implementation:

function curry(int $arity, callable $function, ...$previousArgs): callable
{
    return fn($arg) =>
        $arity <= count([...$previousArgs, $arg])
            ? $function(...[...$previousArgs, $arg])
            : curry($arity, $function, ...[...$previousArgs, $arg]);
}

$glueThreeValues = curry(
    4, 
    fn($glue, $firstVal, $secondVal, $thirdVal) => 
        implode($glue, [$firstVal, $secondVal, $thirdVal])
);
assertEquals("1-2-3", $glueThreeValues("-")(1)(2)(3));

The reader will decide if the benefit is worth the relative ugliness.
Use responsibly!


Camille Hodoul

I'm a JavaScript and PHP developer living in Marseille, France.
Twitter, Github, Flickr