Skip to content

Step 1 of 5 · Reading · ~3 min

Learn

Functions

Closures and Arrow Functions

Sometimes a function is too small to deserve a name, and what you really want is to hand a piece of behaviour to something else — a sort, a filter, a transformation. PHP calls those values closures, and they live in ordinary variables.

A function in a variable

<?php
$double = function ($n) {
    return $n * 2;
};
echo $double(5), "\n";

//> 10

Note the semicolon after the closing brace. This is an assignment statement, not a function declaration.

Closures do not capture automatically

This is the rule that catches everyone. A closure's body starts with the same empty scope a named function gets. To pull an outer variable in, you list it in a use clause.

<?php
$rate = 1.5;

// ✗ $rate is undefined inside — the multiplication has nothing to work with
$broken = function ($kg) { return $kg * $rate; };

// ✓ opt in, explicitly
$byWeight = function ($kg) use ($rate) { return $kg * $rate; };
echo $byWeight(4), "\n";

//> 6

use ($rate) copies the value at the moment the closure is created. use (&$rate) shares the variable itself, which is how you build a counter:

<?php
$count = 0;
$tick = function () use (&$count) { $count++; };
$tick(); $tick(); $tick();
echo $count, "\n";

//> 3

Arrow functions — shorter, and they capture for you

PHP 7.4 added fn, and this course's grader runs 7.4, so you can use it in the exercises.

<?php
$rate = 1.5;
$byWeight = fn($kg) => $kg * $rate;   // no use clause needed
echo $byWeight(4), "\n";

//> 6
function () use (...) { ... }fn(...) => ...
BodyMany statementsExactly one expression
ReturnAn explicit returnImplicit — the expression is the result
CaptureA manual use listAutomatic, by value
By referenceuse (&$x) is availableNot available

Because the capture is by value and happens when the arrow function is defined, later changes to the outer variable never reach it:

<?php
$factor = 2;
$snap = fn($n) => $n * $factor;
$factor = 10;
echo $snap(5), "\n";

//> 10

Passing behaviour into a function

A parameter typed callable accepts anything callable — a closure, an arrow function, or the name of a built-in function as a plain string. You invoke it by putting parentheses straight after the variable.

<?php
function applyTwice(int $n, callable $op): int {
    return $op($op($n));
}
echo applyTwice(2, fn($x) => $x * $x), "\n";
echo applyTwice(2, fn($x) => $x + 3), "\n";
echo applyTwice(3, 'abs'), "\n";

//> 16
//> 8
//> 3

This is the entire reason closures exist. usort is the classic example: you supply the comparison, PHP supplies the sorting.

<?php
$parcels = [["Porto", 5], ["Lisbon", 12], ["Faro", 1]];
usort($parcels, fn($a, $b) => $b[1] <=> $a[1]);
foreach ($parcels as $p) { echo $p[0], " ", $p[1], "\n"; }

//> Lisbon 12
//> Porto 5
//> Faro 1

<=> is the spaceship operator: it returns a negative number, zero, or a positive number, which is exactly the contract a comparison callback has to satisfy.

Grader note: PHP 8.1 added first-class callable syntax — writing strlen(...) to get a callable value — and PHP 8.0 added named arguments. Neither parses on this course's PHP 7.4 grader. Use fn, function () use (...), or a plain 'functionName' string.

Your exercise

Apply Twice asks you to complete function applyTwice(int $n, callable $op): int so that it runs $op on $n, then runs $op again on that result.

The starter passes in fn($x) => $x * $x, so squaring twice has to produce the fourth power. The mistake the grader catches is applying the operation once and then scaling: for the first test's input of 2, $op($n) on its own returns 4 and $op($n) * 2 returns 8, where 16 is expected. The correct body nests the call inside itself, and it prints nothing — the starter's echo already does that for you.

Up nextArrays — One Type, Two RolesArrays

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…