Skip to content
Educora
Beginner18 min5 / 10

Functions, arrow functions and closures

Write functions with typed parameters and return types, use default and named arguments, arrow functions and closures, and process arrays with array_map, array_filter and usort.

Check yourself
In this lesson you will learn
  • Write functions with parameter and return types and explain what strict_types does
  • Use default, named and variadic arguments
  • Apply array_map, array_filter, array_reduce and usort with arrow functions and closures

Imagine you calculate a product's price with VAT in ten different places on a shop website. When the tax rate changes, you have to fix ten places, and you are sure to miss one. A function solves this: you write the code once, give it a name and call it wherever you need.

Typed functions

A function is declared with the function keyword. In modern PHP it is standard to write the parameter types (float $price) and the return type (: float). A function that returns nothing is marked : void, one that may also return null uses ?string, and a parameter that accepts one of several types is written like int|float.

PHP
<?php

declare(strict_types=1);

function fullPrice(float $price, int $quantity = 1): float
{
    return $price * $quantity;
}

function greet(string $name): string
{
    return "Hello, $name!";
}

echo greet("Aysel"), "\n";
echo fullPrice(2.5, 4), "\n";
echo fullPrice(7.5), "\n";
Expected output
Hello, Aysel!
10
7.5

declare(strict_types=1); on the first line of a file turns on strict mode. In this mode PHP does not let you pass the string "5" to an int parameter and throws a TypeError. Without strict mode, PHP tries to convert the value, and mistakes stay hidden. The only exception: an int can always be passed to a float parameter.

Untyped: the bug hides
function add($a, $b)
{
    return $a + $b;
}

echo add("5", "3"); // 8, works by accident
Typed: the bug shows at once
declare(strict_types=1);

function add(int $a, int $b): int
{
    return $a + $b;
}

echo add("5", "3"); // TypeError
Types are a function's “contract”: your editor reads them too and points out mistakes while you are still typing.

Default, named and variadic arguments

If a parameter has a default value, you can leave it out of the call. Since PHP 8 there are named arguments: by writing bold: true you skip the parameters in between and pass the one you need by its name. ...$numbers collects any number of arguments into an array, and in a call, ...$marks “unpacks” an array into separate arguments.

PHP
<?php

function makeTag(string $text, string $tag = "p", bool $bold = false): string
{
    $inner = $bold ? "<b>$text</b>" : $text;
    return "<$tag>$inner</$tag>";
}

function average(float ...$numbers): float
{
    return array_sum($numbers) / count($numbers);
}

echo makeTag("Hello"), "\n";
echo makeTag("Title", "h1"), "\n";
echo makeTag("Warning", bold: true), "\n";
echo average(4, 5, 3), "\n";
$marks = [5, 5, 4, 4];
echo average(...$marks), "\n";
Expected output
<p>Hello</p>
<h1>Title</h1>
<p><b>Warning</b></p>
4
4.5

Anonymous functions and closures

In PHP a function is a value: you can store a nameless function in a variable and call it later. To see an outside variable, an anonymous function “captures” it with use ($rate) — such a function is called a closure. An arrow function fn($x) => ... (PHP 7.4) is shorter: it consists of a single expression and captures outside variables automatically.

PHP
<?php

$rate = 1.7;

$toAzn = function (float $usd) use ($rate): float {
    return $usd * $rate;
};
$toUsd = fn(float $azn): float => round($azn / $rate, 2);

echo $toAzn(10), "\n";
echo $toUsd(34), "\n";

function makeCounter(): Closure
{
    $count = 0;
    return function () use (&$count): int {
        return ++$count;
    };
}

$next = makeCounter();
$next();
$next();
echo $next(), "\n";
Expected output
17
20
3

use ($rate) captures a copy of the value. use (&$count) refers to the variable itself, so the counter grows with every call and lives on after makeCounter has finished. Arrow functions always capture by value and cannot change an outside variable.

Functions inside array functions

Closures are used most of all with array functions. array_map transforms every element, array_filter keeps the elements that pass a test, array_reduce “folds” an array into one value, and usort sorts using your comparison function. Below we add 18% VAT to prices and rank students by score.

PHP
<?php

$prices = [12, 45, 7, 30];

$withVat = array_map(fn($p) => $p * 1.18, $prices);
echo implode(", ", $withVat), "\n";

$expensive = array_filter($prices, fn($p) => $p > 10);
print_r($expensive);

$total = array_reduce($prices, fn($sum, $p) => $sum + $p, 0);
echo $total, "\n";

$students = [
    ["name" => "Murad", "score" => 78],
    ["name" => "Leyla", "score" => 95],
    ["name" => "Elvin", "score" => 86],
];
usort($students, fn($a, $b) => $b["score"] <=> $a["score"]);
echo implode(", ", array_column($students, "name")), "\n";
Expected output
14.16, 53.1, 8.26, 35.4
Array
(
    [0] => 12
    [1] => 45
    [3] => 30
)
94
Leyla, Elvin, Murad

Key points

  • Write parameter and return types; declare(strict_types=1) forbids automatic conversions and exposes mistakes.
  • Parameters with default values can be left out, and named arguments (bold: true) let you skip the ones in between.
  • ...$args gathers arguments into an array, and ...$array in a call unpacks an array into arguments.
  • A closure captures outside variables with use (by reference with &), while an arrow function fn captures them automatically, by value.
  • array_map, array_filter, array_reduce and usort with closures process arrays briefly and clearly; array_filter keeps the keys.

Check yourself

10 questions. Every correct answer earns XP.

1 / 10
A file has declare(strict_types=1);. What happens if we call function add(int $a, int $b): int as add("5", "3")?