Variadic Functions and Argument Unpacking
Before PHP 5.6, a function that took any number of arguments meant func_get_args(): no signature, no types, nothing an IDE or a reader could see. The variadic ... made "any number of these" a declared, typed part of the signature.
// Before: the signature is a lie. Arguments appear from nowhere.
function sum() {
return array_sum(func_get_args());
}
// After: the signature tells the truth, and the type is enforced.
function sum(int ...$numbers): int {
return array_sum($numbers);
}
sum(1, 2, 3); // 6
sum(1, 'two'); // TypeError, caught at the boundary
The same ... works in the other direction. Instead of call_user_func_array(), you unpack an array straight into an argument list, and PHP 8.1 extended it to string keys so it unpacks into named arguments.
$args = [1, 2, 3];
sum(...$args); // 6
// Unpacking with string keys becomes named arguments (8.1+)
function makeUser(string $name, int $age, string $city = 'Sarajevo') {}
$data = ['name' => 'Kemo', 'age' => 34];
makeUser(...$data); // name: 'Kemo', age: 34
// Forwarding every argument, whatever they are, with types intact
function logged(callable $fn): callable {
return function (...$args) use ($fn) {
error_log('calling with ' . count($args) . ' args');
return $fn(...$args);
};
}
That last pattern is the one you reach for constantly: decorators, proxies, and middleware all need to forward arguments they know nothing about. ...$args in, ...$args out, and every type declaration downstream still applies.
Significance: Honest Signatures
A signature is a contract.
func_get_args() broke the contract silently: the function accepted things its own declaration never mentioned, so static analysis, IDE completion, and reflection were all blind. Variadics made the variable part of the contract explicit and typed, which is why every modern PHP codebase forwards with ...$args and nobody writes call_user_func_array() by choice anymore.