PHP 8.0: Named Parameters

Version8.0
TypeNew Feature

PHP 8.0 supports optionally calling functions and class methods by specifying the parameter name, instead of calling them on the order of parameters that they are declared.

PHP, and many other programming languages, support positional parameters: The caller passes the parameters in the same order the function/method declares its parameters.

In PHP 8.0, Named Parameters support is added, which means it's now possible to call a function/method by setting the parameters by their name.

Positional Parameters

function str_contains(string $haystack, string needle): bool {}
str_contains('FooBar', 'Foo');

Named Parameters

function str_contains(string $haystack, string $needle): bool {}
str_contains(haystack: 'FooBar', needle: 'Foo');

With Named Parameters, the call-site names the parameter value, and passes it to the called function; The order of the parameters are not important, as long as all the required parameters are passed.

For example, both of these statements are correct and functionally identical:

str_contains(haystack: 'FooBar', needle: 'Foo');
str_contains(needle: 'Foo', haystack: 'FooBar');

Named parameter feature offers more readability when calling a function with parameters that do not require to have a particular order, or cannot be made more intuitive.

setcookie(
    name: "Awesome",
    value: 'Yes',
    path: "/",
    domain: "example.com",
    secure: true,
    httponly: true
);

The name of the parameter in declaration, without the $ sign will be the parameter name.

Optional Parameters and Default Values

When a function/method declares a parameter with a default value, and if the named parameter call-site does not set a value for that parameter, the default value will be used, just like a positional parameter call.

function greet(string $name, string $greeting = 'Hello') {
    echo "$greeting, $name";
}

greet('Simon');        //"Hello, Simon"
greet(name: 'Simon'); // "Hello, Simon"

Skipping Parameters

Named parameters inherit the default values if not explicitly set at the call-site. This makes it possible to skip optional parameters for a function/method call, which was not possible with positional parameters without assuming the default values.

function greet(string $name, string $greeting = 'Hello', bool $warm = false) {
    echo "$greeting, $name";
    if ($warm) {
        echo "You are awesome";
    }
}

With positional parameters, it is not possible to set a value for the third $warm parameter without assuming the default value for the second $greeting parameter.

greet('Simon', 'Hello', true);

With named parameters, the default value is inherited if it is not set at the call-site:

greet(name: "Simon", warm: true);

Named parameters require all non-optional parameters to be passed. If there are not enough parameters, an `ArgumentCountError exception will be thrown, just like it does with positional parameters.

str_replace(search: 'foo');
Fatal error: Uncaught ArgumentCountError: str_replace() expects at least 3 arguments, 1 given in ...

Unknown Parameters

It is not allowed to pass additional parameters or unknown parameters in a named parameter call-site, and will result in an error:

str_replace(time_limit: "mi");
Fatal error: Uncaught Error: Unknown named parameter $time_limit in ...:...

Overwriting Parameters

It is not allowed to overwrite a parameter that's set before:

function test($a, $b, ...$args) {}
test(a: 28, b: 15, a: 45);
Fatal error: Uncaught Error: Named parameter $a overwrites previous argument in ...:...

Variable Named Parameters

The parameter name cannot be a variable, and will result in a syntax error.

str_replace($key: "Inevitable", $value: "Iron Man", subject: "I am Inevitable");
Parse error: syntax error, unexpected token ":", expecting ")" in ... on line ...

For call-sites with variable parameter names, it is possible to use call_user_func_array() function for this, but be cautious when accepting a user-input with this function.

$key = 'search';
$value = 'replace';
$params = [
    $key => 'Inevitable',
    $value => 'Iron Man',
    'subject' => 'I am Inevitable',
];

call_user_func_array('str_replace', $params);

See call_user_func_array Considerations for caveats when using this pattern.

Mixing Named and Positional Parameters

It is possible to use named parameters and positional parameters in the same call. Named parameters must be used after positional parameters.

setcookie("Awesome", "Yes",
    path: "/",
    domain: "example.com",
    secure: true,
    httponly: true
);

In the snippet above, the first two parameters are positional parameters, as they are passed in the same order they are declared. The last four parameters use named parameters, and the call passes all required parameters, making it a valid call.

However, it is not allowed to use named parameters before positional parameters, if they are used at all:

setcookie(
    name: "Awesome",
    "Yes",
    path: "/",
    domain: "example.com",
    secure: true,
    httponly: true
);
Fatal error: Cannot use positional argument after named argument in ... on line ...

The snippet above has a named parameter for the $name parameter, but uses positional parameter for the second parameter. This is not allowed because the parser cannot correctly infer the position of the parameter once named parameters are used in a call-site.

call_user_func_array Considerations

The call_user_func_array() function will use they array keys as parameter names, and this can be a breaking-change in certain situations, where the parameter array is an associative array with non-numeric keys.

$params = [
    'replace' => 'Iron Man',
    'search' => 'Inevitable',
    'subject' => 'I am Inevitable',
];

echo call_user_func_array('str_replace', $params);

In the snippet above, only PHP 8.0 and later will consider named parameters, and in prior versions, the arguments will be passed as positional parameters.

The snippet below will return different values in PHP < 8.0 and PHP >= 8.0:

$params = [
    'replace' => 'Iron Man',
    'search' => 'Inevitable',
    'subject' => 'I am Inevitable',
];

echo call_user_func_array('str_replace', $params);

PHP < 8.0

I am Inevitable

PHP >= 8.0

I am Iron Man

Due to PHP's back-porting policy, it is unlikely that PHP 7.4 will emit any warnings of potentially different interpretations in PHP 8.0.

In practice, this means all call_user_func_array() function calls must be aware that PHP 8.0 will interpret associative arrays and numeric arrays different.

As a precautionary measure, call_user_func_array() calls can use array_values if the parameters array might contain non-numeric keys.

$params = [
    'replace' => 'Iron Man',
    'search' => 'Inevitable',
    'subject' => 'I am Inevitable',
];

echo call_user_func_array('str_replace', array_values($params));

With array_values call, PHP will always use the positional calling pattern, ensuring the outcome will be the same in PHP 8.0 and later.

call_user_func_array() behaves differently if the parameters array is an associative array. There is a non-zero backwards-compatible impact due to this, and is the only potential backwards-compatibility issue related to named parameters.

Argument Unpacking

PHP supports argument unpacking feature since PHP 5.6, but it did not allow arrays with non-numeric keys, to ensure there will be no backward compatibility issues when named parameters are implemented.

With PHP 8.0 named parameters, it is now possible to use argument unpacking operator with names parameters, although directly using named parameters might be more readable.

str_replace(...[
    'replace' => 'Iron Man',
    'search' => 'Inevitable',
    'subject' => 'I am Inevitable',
]);

Named Parameter with Inheritance Chains

PHP's Named Parameters feature allows renaming the parameter names.

class Foo {
    public function doSomething(string $param) {}
}
class SubFoo extends Foo{
    public function doSomething(string $myParam) {}
}

When using named parameter, the parameter of the called class must be used, and parameter name of the parent class/classes are not considered.

For example, in the simple inheritance chain above, SubFoo::doSomething methods renames the param parameter to myParam. This is allowed, but when they are used, the renamed parameter name must be used.

$foo = new SubFoo();
$foo->doSomething(myParam: "test");

Calling methods with the previous parameter names will cause an error, as if an unknown parameter is passed.

$foo = new SubFoo();
$foo->doSomething(param: "test");
Fatal error: Uncaught Error: Unknown named parameter $param in ...:...

Behavior of func_get_arg(s) and func_num_args()

With positional parameters, it is possible to pass more parameters that what the function declaration accepts. This pattern is now allowed with named parameters, due to the unknown parameter rule. To collect excessive parameter values without unknown parameter restrictions, use [variadic parameters]{#named-params-variadic-params}

function test(){}
test(a: 28, b: 15);
Fatal error: Uncaught Error: Unknown named parameter $a in ...:...

func_get_arg() and func_get_args() still work with named parameters, but they behave as if positional parameters were used at call-site.

func_get_arg() returns the values in the order they are declared in the function; not the order they were used in a named parameter call-site.

function test($a, $b){
    var_dump(func_get_arg(0));
}
test(b: 15, a: 28); // 28

func_get_args() returns a numeric array in the order parameters are declared, not called, which is the exact same behavior in prior PHP versions.

function test($a, $b){
    var_dump(func_get_args());
}
test(b: 15, a: 28);
array(2) {
    [0]=> int(28)
    [1]=> int(15)
}

It is not possible to get a parameter by the named parameter from call-site: e.g. func_get_arg('c').

Named Parameters with Variadic Parameters

A variadic parameter can collect parameters not assigned to a previous parameter (if any), and their names will be preserved.

function test($a, $b, ...$args) {
    var_dump($args);
}

test(28, 15, june: 6, september: 15, january: "01");
array(3) {
    ["june"]=> int(6)
    ["september"]=> int(15)
    ["january"]=> string(2) "01"
}

This behavior aligns with argument unpacking, that it is possible to pass a variadic arguments list to a callback.

function run_hook(callable $callable, ...$args): mixed {
    return $callable($args);
}
function run_hook(callable $callable, ...$args): mixed {
    return $callable(...$args);
}

run_hook('str_replace', 
    search: 'Inevitable',
    replace: 'Iron Man',
    subject: 'I am Inevitable'
); // "I am Iron Man"

Named Parameters in Public APIs

With named parameters, the name of the function/method parameters become part of the public API, and changing the parameters of a function will be a semantic versioning breaking-change. This is an undesired effect of named parameters feature.

Opting-out of Named Parameters

Except for a variadic parameter containing named parameter, both positional and named parameters will behave similarly within the called function.

Some PHP projects have started to use @no-named-parameters DocBlock attribute to indicate that the function does not prefer to be called with named parameters, and does not provide backwards-compatibility for parameter named.


Backwards Compatibility Impact

The only backwards-compatibility impact is with call_user_func_array(), that it considers an associative array as a named parameter call. This can be mitigated by "normalizing" the parameters array with array_values call:

$params = [
    'replace' => 'Iron Man',
    'search' => 'Inevitable',
    'subject' => 'I am Inevitable',
];

echo call_user_func_array('str_replace', array_values($params));

This ensures the code behaves exactly the same in all PHP versions.


RFC Discussion Implementation