Named Arguments and Variadics in PHP 8

One of the new features of PHP 8 is named arguments. It’s one of those features that I love as an end user developer; although it can be a nightmare for library and framework developers, because argument names are now part of the API for public methods, not simply the argument types, and any change to an argument name becomes a backward-compatible break.

Before the introduction of named arguments, PHP required function and method arguments to be passed by their order according to the function/method signature: so for a method like


public DateTimeImmutable::__construct(string $datetime = "now", ?DateTimeZone $timezone = null)

if you want to create a timestamp for the current time (‘now’) with an explicit timezone of UTC, it was necessary to pass both arguments in order to provide the second timezone argument, even though we want the default value for the first argument:


$now = new DateTimeImmutable('now', new DateTimeZone('UTC'));

With named arguments, we no longer need to pass all optional arguments to a function or method, only those where we want a value other than the default; so if we want to allow PHP to handle the default datetime argument itself, we only need to pass the timezone argument by name:


$now = new DateTimeImmutable(timezone: new DateTimeZone('UTC'));

It can make our code a lot shorter and easier to read, especially when we’re calling methods with a lot of optional arguments.

We can also provide mandatory arguments as named arguments, again making the code easier to read, because the argument name is visible and obvious in our code, so when we’re reading code that’s been written using named arguments, we’re not dependent on our IDE to tell us what the 3rd argument is, or look it up in the documentation if we can’t remember.

And when we’re using named arguments, it doesn’t matter what order we specify them in, so we don’t need to remember whether it’s needle before haystack for string or array searches.

Until PHP 8, argument names in functions and methods were only an internal implementation for those functions, and had no visibility outside. With PHP 8, all that has changed, and it is important for library authors to provide a meaningful name for arguments: that’s the drawback for developers of libraries and frameworks that use generic names like $pValue that don’t provide any significance to our users. And before PHP 8, it was possible to change an argument name without worrying about it, but now such a change becomes a backward-compatible break.

A lot has already been written on other blogs about named arguments, so I’m not going to regurgitate all that here. Instead, I’m going to take a look at the more specific subject of named arguments when used with variadics.


Variadics were introduced in PHP 5.6: when you place the ... variadic (“splat”) operator in front of a function parameter, the function will then accept a variable number of arguments, and the parameter will become an array inside the function. For example:


function total(...$values): float {
    $total = 0.0;
    foreach ($values as $value) {
        $total += $value;
    }

    return $total;
}

We can then call the total() function with any number of arguments:


echo total();            // Gives 0.0
echo total(2);           // Gives 2.0
echo total(2, 3);        // Gives 5.0
echo total(2, 3, 5, 8);  // Gives 18.0

Sometimes called “array unpacking”, we can also pass an array of values when calling the function, also using the ... “spread” operator:


$lineArray = [2, 3, 5, 8];

echo total(...$lineArray);  // Gives 18.0

The biggest problem with array packing and unpacking using the splat and spread operators like this is that it would only work with enumerated arrays: using an associative array would throw a fatal error:


$lineArray = ['value 1' => 2,'value 2' => 3,'value 3' => 5,'value 4' => 8];

echo total(...$lineArray);

Fatal error: Uncaught Error: Cannot unpack array with string keys

At least that was the case from PHP 5.6 to 7.4; but another change in PHP 8 allows array unpacking with associative arrays.

Using associative arrays can give us additional “in-code documentation”, providing a description for the values that we’re passing to the function:


$lineArray = [
    'Purchase Value' => 12.50,
    'Surcharge' => 0.12,
    'Tax' => 0.25,
];

echo total(...$lineArray);  // Gives 12.87

helping to improve the readability of the code: we immediately know the purpose of each value that is being passed to the total() function.


There’s a lot of PHP applications in the world that are written like this, using an array to pass optional values to a method:


class car {
    protected $make;
    protected $model;
    protected $colour;
    protected $engineType;
    protected $engineSize;

    public function __construct(string $make, string $model, array $options) {
        $this->make = $make;
        $this->model = $model;
        $this->colour = $options['colour'] ?? 'Black';
        $this->engineType = $options['engineType'] ?? 'EV';
        $this->engineSize = $options['engineSize'] ?? 1.2;
    }
}

$options = ['colour' => 'Blue', 'engineSize' => 1.4];
$car = new car('Toyota', 'Avensis', $options);

Before PHP 8, it would not have been possible to use a variadic for $options, because it’s an associative array; but with PHP 8, this is valid code:


class car {
    protected $make;
    protected $model;
    protected $colour;
    protected $engineType;
    protected $engineSize;

    public function __construct(string $make, string $model, ...$options) {
        $this->make = $make;
        $this->model = $model;
        $this->colour = $options['colour'] ?? 'Black';
        $this->engineType = $options['engineType'] ?? 'EV';
        $this->engineSize = $options['engineSize'] ?? 1.2;
    }
}

$options = ['colour' => 'Blue', 'engineSize' => 1.4];
$car = new car('Toyota', 'Avensis', ...$options);

All that we’ve actually done here is add the ... (“splat”) variadic operator before the $options argument in the method signature, and the ... (“spread”) operator in every call to that method.


Why would we want to make this change? There’s no obvious benefits! But what it does allow us to do is demonstrate an interesting feature of named arguments when combined with variadics. If we supply a named argument and value in a method call when there isn’t actually an argument with that name, it gets bundled into the values passed to the variadic argument. It means that we can instantiate a new car using named arguments for those optional values rather than passing them as an associative array.


$car = new car('Toyota', 'Avensis', engineType: 'Diesel', engineSize: 1.6);

And PHP 8.1 will allow us to take this a step further, and can combine named arguments with array packing, as long as the $options array is passed before the named arguments:


$car = new car('Toyota', 'Avensis', ...['colour' => 'Red'], engineType: 'Diesel', engineSize: 1.6);

Passing the named arguments first will result in a fatal error Cannot use argument unpacking after named arguments. Passing the same argument both in the $options array, and as a named argument will also result in a fatal error Named parameter overwrites previous argument.

We can even use the ... operator to pass a full set of arguments to the method: array keys for make and model will map the appropriate values to those named arguments, and all other array entries will be passed to the variadic $options argument.


$options = ['colour' => 'Green', 'engineSize' => 1.2, 'make' => 'Renault', 'model' => 'Clio'];
$car = new car(...$options);

This doesn’t give us anything that we couldn’t do with the basic $options array; but using named arguments and variadics together does give us alternative approaches to achieve the same results. It’s not something that’s readily supported yet by docblocks or IDEs – and I’ve not yet explored how code analysis tools like phpcs, psalm or phpstan will behave when they encounter it; but it does give us a higher degree of readability in the calling code.
And even if you prefer not to combine named arguments with variadics, it’s still always good to be aware that it is an option, and some of the potential pitfalls of doing so.

This entry was posted in PHP and tagged , , , , . Bookmark the permalink.

2 Responses to Named Arguments and Variadics in PHP 8

  1. Pingback: Splat the List: A Proposal to support Variadics in PHP’s list language construct | Mark Baker's Blog

  2. Pingback: Spreading the News – An Exploration of PHP’s Spread Operator | Mark Baker's Blog

Leave a comment