The very useful variadic argument

Variadic argument is the last argument of a PHP function : it is easy to recognize, with its ellipsis, both at definition and call time. This simple option is actually full of magic powers : it collects all incoming arguments into one parameter.

Now, that is the base description of the variadic. There are several interesting derived features and behaviors, worth noting.

Variadic is always optional

There can only be one variadic argument, and it is always optional. Meaning, there is absolute need to provide it, as it will be always transmitted to the function as an array. So, when the argument is skipped, it is transmitted as an empty array.

<?php

function foo(...$b) {
    print_r($b);
}

foo();      // $b === []; 
foo(1);     // $b === [1];
foo(1, 2);  // $b === [1, 2];

?>

Following this, native functions, such as array_unshift(), actually work with only one argument. This is a useless case : there is no need to call array_unshift() to prepend no value, and get back the original array. But it may be useful when that function is called dynamically, of course.

<?php

$a = [1, 2, 3];
array_unshift($a);
print_r($a);       // same old $a

$b = [];           // or, any dynamical build
array_unshift($a, ...$b);

?>

It also means that a variadic parameter cannot have a default value.

Variadic may be referenced

Variadic arguments accept the & option, so it is possible to send several variables, by reference, in the method.

<?php

function array_sort(&...$a) {
    foreach($a as &$b) {
        sort($b);
    }
    unset($b);
}

$a = [2, 1];
$b = [3, 1, 4];

array_sort($a, $b);

print_r($a);
print_r($b);

?>

Make sure that the block of the method handles the references correctly : the &of the $b variable inside array_sort() is crucial here. Otherwise, the arrays will only be sorted inside the function, due to loss of reference.

Variadic may be typed

Parameters may be variadic and typed at the same time. This is a good way to check the content of an array. Use the ellipsis at call time, to spread the arguments, and again in the parameter, to make them again into an array.

<?php

function foo(A ...$a) { /* bla bla bla */ }

$a = [new A, new B, new C];

foo(...$a); 
//Argument #3 must be of type A, C given

class A {}
class B extends A {}
class C {}

?>

Variadic is always an array

This paragraph is a paradox, compared to the previous. The typehints that were introduced in the previous paragraph is for the elements of the array, not for the parameter itself. Do not confuse one for the other.

<?php

function foo(A ...$a) {
    print get_type($a);     // array
    print get_type($a[0]);  // A    (if any)
}

?>

Variadic may not be last, finally

Well, variadic parameters are always the last. But if you are PHP itself, or use func_get_args(), then you can overcome this limitation. A (very) few native functions such as array_intersect_ukey(), array_diff_uassoc(), and array_intersect_uassoc(). They use an anonymous function to process the intersection of two or more arrays. They also have a peculiar signature : the anonymous function is the last argument, and the variadic is just before, as the early penultimate.

Thanks to Ricardo Boss, who rose the question on the PHP community on Twitter. The problem was related to the following error message : ‘Cannot use positional argument after argument unpacking’.

<?php

$arrays = [$array1, $array2];
print_r(array_intersect_uassoc(...$arrays, "strcasecmp"));

?>

Using named parameters lead to the strange array_intersect_uassoc() does not accept unknown named parameterserror message, so, in the end, good old calluserfunc_array is a solution, albeit a good one.

<?php

// From the PHP manual 
$array1 = ["a" => "green", "b" => "brown", "c" => "blue", "red"];
$array2 = ["a" => "GREEN", "B" => "brown", "yellow", "red"];
$arrays = [$array1, $array2];

print_r(call_user_func_array('array_intersect_uassoc', [$array1, $array2, "strcasecmp"]));
?>

When building a method with such a signature, where the arbitrary number of elements is not the last one, it is not possible to use the variadic PHP operator. The only solution is the func_get_args()function, which will grab all the arguments, at call time, in an array. It is old technology, and all the modern options (default values, typehints, names) will not be available.

Variable list of arguments are cool

Variadic is a surprisingly useful operator, both for collecting arguments and processing them. Coupled with arrays, they are a very convenient way to pass a load of values to a method, and receive them in a convenient packaging.

They are also a trick of the trade : it is often efficient to process multiple similar arguments with a loop, inside the method, rather than make multiple successive calls to that method. Variadic provides a transparent interface for that.

<?php

function foo(...$list) {
  foreach($list as $l) {
    // do something with $l
  }
} 

// called empty
foo();
// called once
foo(1);
// called with multiple arguments
foo(1, 2, 3);
// dynamically called 
foo(...$array);

?>

Until next time, happy PHP coding!