Named Arguments: More Readable Calls with Many Optional Parameters
AI generated
<?php
8.4
PHP · PHP 8.4 · Core Language · Function Calls
Named Arguments: more readable calls with many optional parameters
from positional guesswork to a self-documenting function call

Anyone who calls functions and constructors with many optional parameters knows the long lists of true, false and null whose meaning only becomes clear by looking at the signature. Named arguments solve this problem: the parameter name becomes visible right at the call site, order no longer matters, and unused defaults never have to be spelled out artificially.

10 min read named arguments · positional arguments · variadics · constructor promotion PHP 8.0 · 8.1 · 8.2 · 8.3 · 8.4

1. Framing: syntax and purpose of named arguments

Since PHP 8.0, every function, method and constructor call can use the syntax parameterName: $value, binding the value not to its position in the argument list but to the exact name the parameter carries in the function signature. This feature, called named arguments, binds the supplied value to the exact name, not to whatever variable name the caller happens to use, because PHP resolves the binding at call time against the signature of the called function.

The purpose of this syntax is primarily readability at the call site. A call like createUser('Anna', 'anna@example.com', true, false) does not reveal what the two boolean values stand for without looking up the signature of createUser(). With named parameters, the same call becomes createUser(name: 'Anna', email: 'anna@example.com', isAdmin: true, sendWelcomeMail: false), documenting itself without any extra comment in the caller's code.

They work identically with ordinary functions, static and non-static methods, closures, first-class callables and constructors. Named arguments are pure call syntax and do not change the function signature itself, which is why an existing function can be called this way immediately, without any change to its definition, as soon as the caller runs on PHP 8.0 or newer.

2. Skipping optional parameters on purpose

The practical core benefit of named arguments shows up in functions with several optional parameters. Without named arguments, a caller who only wants to set the last of four optional parameters is forced to repeat the default values of the three before it, just to reach the position where the actually desired parameter sits. That leads to calls that spell out a default explicitly even though no deviation from the standard behavior is intended at all.

With named arguments, this artificial repetition disappears entirely. The caller names only the parameters whose value should actually deviate from the default, leaving everything else at its standard value as defined in the signature. This not only reduces the character count of the call, it also makes immediately clear which values actually matter for this specific call, since everything else stays implicitly at its default.

Important: an optional parameter skipped via named arguments still needs a default value in the signature. If the default is missing and the parameter is filled neither positionally nor by name, PHP throws an ArgumentCountError, because named arguments change nothing about the basic requirement to fill every parameter without a default.


<?php

declare(strict_types=1);

function createUser(
    string $name,
    string $email,
    bool $isAdmin = false,
    bool $sendWelcomeMail = true,
    ?string $locale = null,
): array {
    return compact('name', 'email', 'isAdmin', 'sendWelcomeMail', 'locale');
}

// Only the parameter that actually deviates from its default is named,
// isAdmin and locale simply keep their declared default values
$user = createUser(
    name: 'Anna',
    email: 'anna@example.com',
    sendWelcomeMail: false,
);

3. Combining positional and named arguments

PHP allows mixing positional arguments and named arguments in a single call, but under one fixed rule: once an argument has been passed by name, every argument that follows must also be named. Positional arguments may therefore only appear at the start of the argument list, followed exclusively by the named form. This rule prevents ambiguities that would arise if PHP had to switch back to positional logic after a named argument.

A second central point when combining the two: the same parameter must never be filled twice, neither twice positionally nor once positionally and once by name. If a call passes three positional arguments, for example, but the third parameter is then additionally addressed again via named arguments, PHP aborts with an Error that names exactly which parameter was addressed twice. This check happens already during argument resolution, long before the actual function body executes.

In practice the combination proves itself particularly for functions whose first one or two parameters are always set for business reasons, while the rest stay optional. The required parameters then stay short and concise as positional arguments, while the rarely set optional parameters get readable names via named arguments, without switching the whole call to the named form.


<?php

declare(strict_types=1);

function createUser(
    string $name,
    string $email,
    bool $isAdmin = false,
    bool $sendWelcomeMail = true,
    ?string $locale = null,
): array {
    return compact('name', 'email', 'isAdmin', 'sendWelcomeMail', 'locale');
}

// Positional for the two always-required parameters,
// named for everything that deviates from the default
$user = createUser('Anna', 'anna@example.com', sendWelcomeMail: false);

// This raises an Error: "Named parameter $email overwrites previous argument"
// $broken = createUser('Anna', 'anna@example.com', email: 'other@example.com');

4. Named arguments and constructor property promotion

Constructor property promotion and named arguments were both introduced with PHP 8.0 and complement each other especially well in practice. A promoted constructor parameter is simultaneously the declaration of a class property and an ordinary constructor parameter, which is why it can be addressed via named arguments exactly like any other parameter. The parameter name in the constructor usually matches the property name, which makes the named call especially readable, since the name at the new call matches the later property access.

This combination pays off especially for immutable value objects with readonly properties. A class with three or four promoted parameters, only one of which should deviate from the default, benefits directly from named arguments: the caller does not need to memorize the order of the constructor parameters and never writes out default values artificially. This also reduces the risk of accidentally swapping two adjacent parameters of the same type, a mistake that, from experience, happens frequently with purely positional calls that chain several int or string parameters in a row.

Libraries that rely on immutable configuration objects benefit especially strongly from this combination: new optional parameters can be added at the end of the constructor without breaking existing callers, as long as those callers use named arguments instead of purely positional ones, since the position of a new parameter appended at the end simply does not affect named callers.


<?php

declare(strict_types=1);

final class Money
{
    public function __construct(
        public readonly int $amount,
        public readonly string $currency = 'EUR',
        public readonly int $precision = 2,
    ) {
    }
}

// Constructor property promotion plus named arguments:
// only the deviating parameters are spelled out
$price = new Money(amount: 1999, currency: 'USD');

echo sprintf('%d %s', $price->amount, $price->currency);

5. Named arguments and variadic parameters

When a call using named arguments meets a variadic function with ...$args, a separate rule applies: every named argument whose name does not match a fixed, previously declared parameter is collected as an additional entry in the variadic array, using the argument name as a string key rather than a numeric index. Inside the function, $args is then an associative array whose keys correspond exactly to the parameter names used at the call site.

This differs noticeably from a purely positional call to the same variadic function, where $args is a numerically indexed array. A function body meant to support both call styles therefore has to account for the fact that the keys of $args may be either integers or strings depending on the caller, and must not blindly rely on either form once named arguments are used together with variadic parameters.

Closely related but technically distinct is unpacking arrays with string keys via the spread operator, possible since PHP 8.1: foo(...['name' => 'Anna']) passes name as a named argument, regardless of whether foo() is variadic itself. This spread syntax allows a runtime-assembled associative array to be passed directly as a set of named arguments to a function, which is especially useful for dynamically built option lists.


<?php

declare(strict_types=1);

function logEvent(string $message, string ...$tags): void
{
    // Named arguments beyond the declared $message parameter
    // become string keys in the variadic $tags array
    foreach ($tags as $key => $value) {
        echo "{$key}: {$value}\n";
    }
}

logEvent(message: 'Deploy finished', environment: 'production', pipeline: 'release');

// Spreading an associative array into named arguments (PHP 8.1+)
$options = ['environment' => 'staging', 'pipeline' => 'nightly'];
logEvent('Deploy finished', ...$options);

6. Readability with many boolean flags

Functions with several consecutive boolean parameters are among the spots in code where named arguments bring the biggest readability gain. A call like configure(true, false, true, false) forces every reader to check the signature of configure() to understand which true stands for which flag, and this context switch costs time on every single read. With named arguments, the same call becomes configure(cache: true, debug: false, strict: true, verbose: false) and is understandable without ever looking at the definition.

This effect intensifies once several flags share the same type and can easily be swapped when purely positional. Two adjacent bool parameters in the wrong order produce no type error that PHP or a static analysis tool like PHPStan could catch at compile or analysis time, because both values are valid for the parameter type. Named arguments make this whole class of bugs impossible from the start, because the name, not the position, decides the mapping.

For functions that take only boolean flags, switching to named arguments in the caller's code is often the more pragmatic solution compared to a larger overhaul of the signature, for example toward a dedicated options object or enum parameters. Named arguments do not change the function itself and can therefore be introduced gradually in existing caller code, without touching the signature of the called function.

7. BC pitfalls: renaming a parameter

Named arguments make a function's parameter name part of its public interface, an aspect that was practically irrelevant before PHP 8.0. As long as calls were purely positional, a parameter could be renamed freely during refactoring without affecting any caller. But once any caller addresses the same parameter via named arguments, a seemingly harmless rename becomes a breaking change that only shows up as an error at runtime.

Concretely, PHP throws an Error in this case with a message like "Unknown named parameter", as soon as the caller uses the old, no-longer-existing parameter name. Positional callers of the same function, on the other hand, do not notice the rename at all, as long as the type and order of the parameters have not changed. This asymmetry means the same signature change has completely different effects depending on the calling style.

For public library APIs, a clear consequence follows: parameter names must be versioned with the same care as method names themselves from the point on where named arguments can be used against them. A rename therefore belongs in a major release, not a patch or minor release, and should be explicitly flagged in the changelog as potentially breaking for callers using named arguments.


<?php

declare(strict_types=1);

// Public API, version 1.0
final class ImageResizer
{
    public function resize(int $width, int $height): void
    {
        // ...
    }
}

$resizer = new ImageResizer();
$resizer->resize(width: 800, height: 600);

// "Harmless" refactoring in version 1.1, positional callers still work fine
final class ImageResizerV2
{
    public function resize(int $newWidth, int $newHeight): void
    {
        // ...
    }
}

// Fatal error: Unknown named parameter $width
$broken = (new ImageResizerV2())->resize(width: 800, height: 600);

8. Named arguments with internal PHP functions: limits and quirks

Since PHP 8.0, the parameter names of internal, C-implemented functions officially count as part of the public API, so named arguments can also be used against functions like htmlspecialchars() or array_slice(). Before this change, parameter names of internal functions were occasionally adjusted between versions without this counting as a breaking change, because nobody could address them by name. That freedom has not existed in the same form since named arguments were introduced.

Some internal functions with complex signatures, for example ones with variadic or by-reference parameters, still had incomplete support for named arguments in early PHP 8.0 patch releases and were improved in subsequent versions. Anyone using named arguments against an internal function should, when in doubt on older, deprecated PHP versions, check the manual page of that function for whether and since which version the documented parameter name is stable.

For your own code this means above all one thing: named arguments against internal functions are only syntactically valid at all from PHP 8.0 upward. A codebase that still needs to support an older PHP version cannot use named arguments conditionally, but still needs the purely positional form at those call sites until the minimum version is raised.

Task Positional arguments only With named arguments Advantage
Setting several optional flags setOptions(true, false, null, true, 'de') setOptions(cache: true, locale: 'de') Immediately readable, order irrelevant
Changing only a middle parameter Must repeat every default before it Name only the desired parameter No artificial boilerplate in the call
Skipping one of many optional parameters slice($arr, 2, null, true) slice($arr, offset: 2, preserveKeys: true) No null placeholders needed
Constructor with many optional properties Must memorize parameter order new Money(amount: 1999, currency: 'USD') Self-documenting call
Distinguishing several boolean flags create('Anna', true, false, true) create(name: 'Anna', isAdmin: false, sendMail: true) Swapping adjacent flags ruled out

9. Best practices: when named arguments make sense

Named arguments deliver the biggest benefit for functions and constructors with several optional parameters, especially when several of them share the same type or are purely boolean in nature. In these cases, the readability gain at the call site clearly outweighs any extra typing, and the risk of swapped but type-compatible arguments drops noticeably. For rarely used options buried deep in the parameter list, named arguments are almost always the better choice compared to writing out every preceding default.

Named arguments are less useful for functions with just one or two obvious required parameters whose meaning is already clear from the function name and context, for example strlen($text). Here the named form adds hardly any extra clarity but lengthens the call unnecessarily. Nor do named arguments substitute for a poorly named signature: if a parameter carries a cryptic or generic name like $value or $data, naming it at the call site does little good as long as the name itself says nothing.

For public library APIs, it is worth choosing parameter names as carefully as public method names from the very start, because they become part of the stable contract through named arguments. Within a closed codebase where all callers evolve together with the signature, this risk is lower, but consistency still pays off here too, so that named arguments are named predictably across different functions.

10. Summary

Named arguments solve a very concrete readability problem: calls to functions and constructors with many optional parameters are freed from positional guesswork and instead name directly which value is meant for which parameter. Optional parameters can be skipped on purpose, positional and named arguments combine under clear rules, and the combination with constructor property promotion makes value objects and configuration classes in particular noticeably more readable.

At the same time, named arguments bring new responsibility: the parameter name becomes part of a function's public interface, a rename during refactoring can break callers using named arguments, even though positional callers remain unaffected. Anyone who knows this rule, correctly understands variadic parameters, and uses named arguments deliberately for many optional or boolean parameters gains noticeable clarity in caller code without opening up new sources of error.

Named arguments in PHP, the essentials at a glance

Syntax

parameterName: $value binds the value to the declared parameter name, regardless of its position in the call.

Combining

Positional arguments may come first, followed only by named arguments. The same parameter may never be filled twice.

Variadics

Unknown named arguments land as string keys in the variadic array instead of being numerically indexed.

BC risk

Parameter names are part of the public API. Renaming breaks callers using named arguments, positional callers remain unaffected.

11. FAQ: Named Arguments in PHP

1What are named arguments in PHP?
A call syntax since PHP 8.0 that binds an argument via parameterName: $value to the declared parameter name, instead of relying solely on position.
2Available from which PHP version?
Since PHP 8.0. In older versions, the syntax parameterName: $value is invalid and produces a parse error.
3Combinable with positional arguments?
Yes, as long as positional arguments come first. After the first named argument, every following one must also be named.
4Order needs to match the signature?
No, any order is allowed, since the mapping happens by name rather than by position.
5Filling a parameter twice?
Results in an Error, since every parameter may only be filled exactly once per call, whether positional or named.
6Do they work with constructor promotion?
Yes, promoted constructor parameters are ordinary parameters and can be addressed identically via named arguments.
7Effect on variadic parameters?
Unknown named arguments land in the variadic array with the name as a string key instead of a numeric index.
8Is renaming a breaking change?
For named callers yes, they get an Error. Positional callers do not notice the rename at all.
9Do all internal functions support this?
Generally yes since PHP 8.0, though a few complex signatures had incomplete support in early patch releases.
10When should I skip this?
For one or two obvious required parameters whose meaning already follows from the function name.

Mironsoft

PHP architecture, code quality and Magento development

Want more readable function calls in your own project?

We review existing PHP code for unreadable positional calls and replace them selectively with named arguments, with full type safety and PHPStan coverage at level 5 and above.

Code review

Analysis of unreadable positional calls and proposals for named-argument refactorings

Refactoring

Migration of existing constructors and functions to named arguments

PHPStan coverage

Static analysis at level 5 and above for new named-argument calls