Lesson 60 of 100
Functions15 min5 checksPHP 8.5

Accept Variable Arguments with the ... Operator

Learn how a PHP function collects any number of arguments into an array with ..., how to type them, and how to unpack arrays into calls.

01 · THE IDEAREAD → TRY → LEARN

Why variadic functions exist

Some functions naturally take a changing number of inputs: adding up prices, joining name parts, or picking the largest of several scores. You could ask the caller to build an array first, but PHP also lets the function declare that it accepts a variable-length argument list. You do this with the ... token in the parameter list.

Collecting arguments

Place ... before a parameter name. Every argument passed at that position and beyond is gathered into an array assigned to that parameter. Inside the function you treat it like any other array.

Example
<?php
function sum(int ...$numbers): int {
    $total = 0;
    foreach ($numbers as $number) {
        $total += $number;
    }
    return $total;
}

echo sum(1, 2, 3, 4), "\n";
echo sum(), "\n";
Output
10
0

Notice that calling sum() with no arguments is legal. The variadic parameter then holds an empty array, so the loop does nothing and the function returns 0. This is the first edge case to keep in mind: a variadic parameter is optional by nature.

Positional parameters before the variadic one

A variadic function can have ordinary parameters first. Only the trailing arguments that do not match those earlier parameters land in the array. The variadic parameter must be the last one in the list.

Example
<?php
function label_items(string $prefix, string ...$items): array {
    return array_map(fn(string $item): string => $prefix . $item, $items);
}

echo json_encode(label_items("#", "php", "json"), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
$more = ["a", "b"];
echo json_encode(label_items("-", ...$more), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
["#php","#json"]
["-a","-b"]

The second call shows the other use of ...: at the call site it unpacks an array (or a Traversable) into separate arguments. label_items("-", ...$more) behaves just like label_items("-", "a", "b"). The same token therefore works in two directions: in a definition it packs, in a call it unpacks.

Typing the collected values

You can put a type declaration before .... Then every collected argument must satisfy that type. With int ...$numbers, each value is checked individually. In the default non-strict mode, a numeric string such as "2" is coerced to an integer, while a value such as "abc" throws a TypeError.

Example
<?php
function sum(int ...$numbers): int {
    $total = 0;
    foreach ($numbers as $number) {
        $total += $number;
    }
    return $total;
}

echo sum(1, "2"), "\n";
try {
    echo sum(1, "abc");
} catch (TypeError $e) {
    echo "TypeError\n";
}
Output
3
TypeError

Typing the variadic parameter is usually worth it. It keeps bad data out of the array so the body of the function can stay simple.

Worked reasoning: largest value with a fallback

Suppose we want max_or_default(int $default, int ...$numbers): int. It returns the largest number given, or $default when no numbers are supplied. Think through it in steps:

  1. The first parameter is a normal required argument, so it is always present.
  2. The remaining arguments arrive in $numbers.
  3. If $numbers is an empty array, nothing was supplied, so return $default.
  4. Otherwise return max($numbers). Calling max() on an empty array throws, which is why step 3 comes first.

Two traps are worth noticing. First, do not seed the running maximum with $default or 0. If every real value is negative, the seed would wrongly win. Second, the default is only a fallback: when numbers exist, it must not take part in the comparison, even if it is larger.

Common mistakes

  • Putting a parameter after the variadic one. Nothing can follow it.
  • Forgetting that the collected value is an array, so count($numbers) and $numbers === [] are the right tools for checks.
  • Calling max() or min() on an empty collected array.
  • Assuming a default value is allowed on the variadic parameter. It is not needed, because the empty array is the natural "nothing passed" state.

Exercise

Implement max_or_default(int $default, int ...$numbers): int. The tests call it with positional arguments, for example max_or_default(0, 3, 9, 4) must return 9, and max_or_default(5) must return 5. Handle negative numbers correctly, and ignore the default whenever at least one number is supplied.

Recap

Use ...$name in a definition to gather extra arguments into an array. Put it last, optionally type it, and expect it to be empty when no extra arguments arrive. Use ... in a call to spread an array into arguments. Handle the empty case explicitly before functions like max().

Before you start

Functions · Arrays · Types

You’ll practice
  • Collect and process variadic scalar arguments
  • Implement and test a deterministic PHP 8.4 function
Your task

Make max_or_default() pass all 5 checks. Run your code as often as you like, then check your answer.

exercise.phpSaved in this browserPHP 8.5
CONSOLE & CHECKS● Ready

Ready when you are

Your output will appear here.
Make it work. Then make it pass.

PHP loads on your first run. Each check starts fresh. Your code stays in this browser; clearing site data removes your progress. A check or the task looks wrong? Report a problem with this exercise.