PHP Named Arguments

Named arguments, introduced in PHP 8.0, let you pass values to a function by specifying the parameter’s name instead of relying purely on its position in the argument list. Instead of remembering “the third parameter controls whether the email is sent,” you write sendEmail: true right at the call site. This makes function calls self-documenting, lets you skip optional parameters you don’t care about, and removes a whole class of bugs caused by arguments being passed in the wrong order.

Overview: How Named Arguments Work

Every PHP function, method, or constructor has a fixed list of parameters, each with a name (like $name or $price) defined in its signature. Traditionally, PHP only cared about the position of the values you passed in a call — the first argument fills the first parameter, the second fills the second, and so on. Named arguments give you a second way to bind values: instead of position, you use the parameter’s name directly in the call, written as parameterName: value.

Internally, when the Zend Engine compiles a function call, it builds an argument list. For positional arguments it simply appends the value in call order. For named arguments, the compiler looks up the parameter’s declared name against the function’s reflection metadata (the same metadata exposed by ReflectionFunction) and slots the value into the matching position before the call executes. This is why the parameter names in a function’s signature are not just cosmetic anymore — from PHP 8.0 onward, they are part of that function’s public contract, exactly like the types of its parameters.

Because named arguments are resolved by name rather than order, you can:

  • Pass arguments in any order, as long as every name matches a real parameter.
  • Skip optional parameters in the middle of the list and only override the ones you actually need to change, letting the rest fall back to their default values.
  • Make call sites read like documentation, especially for functions with several boolean or numeric flags where positional calls are easy to misread (e.g. createUser('Ada', 'ada@example.com', true, false) — what do true and false mean here?).

Named arguments work with user-defined functions, class methods, constructors (including promoted constructor properties), and most internal PHP functions whose parameter names are part of the documented API.

Syntax

The general form places parameterName: before each value, separated by commas, in any order:

<?php
functionName(
    parameterName: $value,
    anotherParameter: $anotherValue,
);
Part Meaning
parameterName Must exactly match a parameter name declared in the function’s signature (case-sensitive).
: Separates the parameter name from the value being passed to it.
$value Any expression — a literal, a variable, a function call, etc.
Order Named arguments can appear in any order relative to each other.
Mixing Positional arguments may come first, followed by named arguments — but never the reverse.

Examples

Example 1: Named vs. positional calls

<?php
function createUser(string $name, string $email, string $role = 'subscriber', bool $active = true): array {
    return [
        'name' => $name,
        'email' => $email,
        'role' => $role,
        'active' => $active,
    ];
}

// Positional call - order matters
$user1 = createUser('Ada Lovelace', 'ada@example.com', 'admin', true);

// Named arguments - order doesn't matter, and intent is explicit
$user2 = createUser(
    email: 'grace@example.com',
    name: 'Grace Hopper',
    role: 'editor',
);

print_r($user1);
print_r($user2);

Output:

Array
(
    [name] => Ada Lovelace
    [email] => ada@example.com
    [role] => admin
    [active] => 1
)
Array
(
    [name] => Grace Hopper
    [email] => grace@example.com
    [role] => editor
    [active] => 1
)

Both calls produce equivalent results, but $user2 reorders email and name and still binds correctly because PHP matches by name, not position. Note that active defaults to true, which print_r() renders as 1.

Example 2: Skipping optional parameters

This is the single biggest practical win of named arguments: you can override just one optional parameter buried in the middle of a long signature, without having to restate every default before it.

<?php
function sendNotification(
    string $message,
    string $channel = 'email',
    bool $urgent = false,
    ?string $cc = null,
    int $retries = 3
): string {
    $flags = [];
    if ($urgent) $flags[] = 'URGENT';
    if ($cc !== null) $flags[] = "cc:$cc";
    $flagText = $flags ? ' [' . implode(', ', $flags) . ']' : '';
    return "[$channel] $message$flagText (retries: $retries)";
}

// Skip channel, urgent, and cc - only override retries
echo sendNotification('Server disk usage above 90%', retries: 5), PHP_EOL;

// Only flip urgent to true, leave everything else at its default
echo sendNotification('Payment gateway is down', urgent: true), PHP_EOL;

Output:

[email] Server disk usage above 90% (retries: 5)
[email] Payment gateway is down [URGENT] (retries: 3)

Without named arguments, overriding $retries alone would force you to also explicitly pass $channel, $urgent, and $cc just to get to it positionally. Named arguments let each call state only what it actually cares about.

Example 3: Named arguments with constructor promotion

Named arguments are especially readable when instantiating classes that use constructor property promotion, since object construction often involves several similarly-typed values (strings, floats) that are easy to mix up positionally.

<?php
final class Product
{
    public function __construct(
        public readonly string $sku,
        public readonly string $title,
        public readonly float $price,
        public readonly float $taxRate = 0.0,
        public readonly int $stock = 0,
    ) {}

    public function priceWithTax(): float
    {
        return round($this->price * (1 + $this->taxRate), 2);
    }
}

$mug = new Product(
    sku: 'MUG-001',
    title: 'Ceramic Mug',
    price: 12.50,
    taxRate: 0.08,
);

echo $mug->title . ': $' . $mug->priceWithTax() . PHP_EOL;
echo "Stock: {$mug->stock}" . PHP_EOL;

Output:

Ceramic Mug: $13.5
Stock: 0

The readonly properties (available since PHP 8.1) are promoted directly from constructor parameters, and named arguments make it obvious which value maps to price versus taxRate — two adjacent floats that would be very easy to swap by accident in a positional call.

Under the Hood: Named Arguments and Array Unpacking

Since PHP 8.1, the spread operator (...) can unpack an array with string keys directly into named arguments, as long as the keys match parameter names. This is how PHP internally treats named-argument calls when the arguments come from a dynamic source, such as form data or a configuration array.

<?php
function greet(string $greeting, string $name): string
{
    return "$greeting, $name!";
}

$args = ['name' => 'Neo', 'greeting' => 'Wake up'];
echo greet(...$args);

Output:

Wake up, Neo!

The engine walks the array, treats each string key as a named argument, and resolves it against the function’s parameter names exactly as if you had typed greet(name: 'Neo', greeting: 'Wake up') by hand. If the array had an integer key instead, that value would be treated as a positional argument at that index. Mixing both kinds of keys in one array is allowed as long as positional keys still come before named ones in the resulting call, matching the same left-to-right rule that applies to literal calls.

Common Mistakes

Mistake 1: Positional argument after a named one

PHP requires all positional arguments to come before any named arguments in a call. Reversing that order is a compile-time error, not a warning:

<?php
function logEvent(string $type, string $message, int $severity = 1) {
    echo "[$type:$severity] $message" . PHP_EOL;
}

// Fatal error: Cannot use positional argument after named argument
logEvent(type: 'auth', 'User login failed', 3);

This fails because once the compiler sees a named argument, it no longer has an unambiguous “next position” to assign a later positional value to. The fix is to either name every argument from that point on, or keep the whole call positional:

<?php
function logEvent(string $type, string $message, int $severity = 1) {
    echo "[$type:$severity] $message" . PHP_EOL;
}

logEvent(type: 'auth', message: 'User login failed', severity: 3);
// or, fully positional:
logEvent('auth', 'User login failed', 3);

Output:

[auth:3] User login failed
[auth:3] User login failed

Mistake 2: Treating parameter names as an implementation detail

Named arguments turn parameter names into part of a function’s public API. Renaming a parameter that other code calls by name is now a breaking change, even if the function’s behavior didn’t change at all:

// Original library function
function scheduleTask(string $name, int $delaySeconds = 0) { /* ... */ }

// Existing call site written against the original signature
scheduleTask(name: 'cleanup', delaySeconds: 30);

// Later, the library author renames the parameter for clarity:
function scheduleTask(string $taskName, int $delaySeconds = 0) { /* ... */ }

// The old call site now fails at runtime:
// Fatal error: Uncaught Error: Unknown named parameter $name
scheduleTask(name: 'cleanup', delaySeconds: 30);

Before PHP 8.0, a library author could freely rename an internal parameter without affecting callers, since only argument order mattered. Now, if your code (or a public library) supports named arguments, treat parameter names with the same care as method names: document them, and avoid renaming them in a released public API without a deprecation path.

Best Practices

  • Use named arguments for functions with several optional or boolean parameters, where a positional call like resize(200, 100, true, false) is hard to read at a glance.
  • Prefer named arguments over positional ones when you only need to override a parameter near the end of a long signature — you avoid restating every default in between.
  • Combine named arguments with constructor property promotion for value objects and DTOs; it keeps object construction both concise and self-explanatory.
  • Be cautious using named arguments against internal (built-in) PHP functions whose parameter names were only formally standardized in PHP 8.0 — double-check the documented names before relying on them.
  • Treat parameter names in any function you expose publicly as part of its API surface; renaming one is a breaking change for callers using named arguments.
  • Don’t force named arguments everywhere — for simple, obvious calls like strlen($text), positional syntax is still clearer and shorter.

Practice Exercises

  • Write a function formatPrice(float $amount, string $currency = 'USD', int $decimals = 2, bool $showSymbol = true). Call it three times using named arguments: once overriding only $decimals, once overriding only $currency, and once passing every argument in a different order than declared.
  • Given a class Rectangle with a constructor __construct(public readonly float $width, public readonly float $height), create two instances using named arguments where the width and height are swapped between the two calls, and print each rectangle’s area to confirm the values were not mixed up.
  • Predict what error (if any) the following call raises, then explain why: a function declared as function connect(string $host, int $port = 3306) is called as connect(port: 3307, 'db.example.com').

Summary

  • Named arguments (PHP 8.0+) let you pass values using parameterName: value instead of relying on position.
  • They can appear in any order relative to each other, but must always come after any positional arguments in the same call.
  • They let you skip optional parameters you don’t need to change, instead of restating every default up to the one you care about.
  • Parameter names become part of a function’s public contract once named arguments are in use — renaming them can break callers.
  • Since PHP 8.1, array unpacking with the spread operator (...$array) treats string keys as named arguments automatically.
  • They pair especially well with constructor property promotion for clear, self-documenting object construction.