Parameter types, default values, and named arguments (PHP 8.0+)
Concept
PHP 8.0 introduced named arguments, fundamentally changing how functions with multiple parameters are called. Before named arguments, every call had to respect positional order, making calls with many parameters fragile and self-documenting only through accompanying comments. Named arguments let you pass values by parameter name, skip optional parameters with defaults, and reorder arguments at the call site — without changing the function signature.
Default parameter values allow callers to omit trailing arguments. The rule is that parameters with defaults must follow parameters without defaults — you cannot have function f(int $a = 1, int $b): void because the required $b comes after an optional $a. PHP enforces this at parse time. Default values must be constant expressions: literals, constants, [], null, or expressions that can be evaluated at compile time. You cannot use a variable or a function call as a default.
Named arguments interact powerfully with built-in functions, particularly those with historically awkward signatures. array_slice($array, offset: 0, length: 5, preserve_keys: true) is clearer than the positional equivalent where true floats unexplained at position four. The named argument preserve_keys: true is self-documenting. This is especially valuable in test factories, data-class constructors, and HTML helper functions.
Type-wise, named arguments do not bypass type coercion rules. The argument is type-checked against the corresponding parameter's declared type exactly as in a positional call. Named arguments also interact with variadic functions: you may pass named extra arguments into a ...$args parameter, where PHP collects them into an associative array keyed by name rather than an indexed array.
Code Example
<?php
declare(strict_types=1);
function createUser(
string $name,
string $email,
int $age = 0,
bool $active = true,
string $role = 'viewer',
): array {
return compact('name', 'email', 'age', 'active', 'role');
}
// Positional — must track argument order mentally
$a = createUser('Alice', 'alice@example.com', 30, true, 'admin');
// Named — skip age, flip role and active order, self-documenting
$b = createUser(
name: 'Bob',
email: 'bob@example.com',
role: 'editor', // skips age and active, both use defaults
);
// Named args with built-ins
$slice = array_slice(array: [1, 2, 3, 4, 5], offset: 1, length: 3, preserve_keys: true);
// Named args into variadic — collected as associative array
function logContext(string $message, mixed ...$context): void
{
echo $message . ' ' . json_encode($context) . PHP_EOL;
}
logContext('Payment processed', amount: 99.99, currency: 'USD', userId: 42);
// Output: Payment processed {"amount":99.99,"currency":"USD","userId":42}
var_dump($b['role']); // string(6) "editor"
var_dump($b['age']); // int(0) — default used
var_dump($b['active']); // bool(true) — default usedInterview Q&A
Q: Named arguments were added in PHP 8.0. What problem were they solving, and what are their constraints?
The core problem was that PHP's built-in functions — and many user-land APIs accumulated over decades — have parameter orders that are impossible to remember and require reading the manual every time (array_splice, str_pad, implode). Named arguments make call sites self-documenting and allow skipping optional parameters without passing explicit null. Constraints: you cannot use named arguments to pass a value twice (once positional, once named). Named arguments must come after all positional arguments at the call site. You cannot use names that conflict with the parameter name defined in the function; if you rename a parameter in a library, callers using named arguments break — this is a backwards-compatibility concern that affects library authors more than application developers.
Q: What are the rules around default parameter values, and why can you not use a runtime expression as a default?
Default values must be constant expressions because PHP resolves them at compile (opcode) time, not at call time. This means only literals (0, '', []), named constants (PHP_INT_MAX), class constants (MyClass::TIMEOUT), and expressions composed entirely of those (e.g., 60 * 60) are valid. A function call like time() or a variable like $config['timeout'] would require runtime evaluation, which contradicts the compile-time resolution model. If you need a runtime default, the standard idiom is function f(int $ttl = null): void { $ttl ??= config('cache.ttl'); } — use null as the sentinel and resolve the actual default in the body.
Q: How do named arguments affect variadic functions, and what does the collected $args look like?
When a caller passes extra arguments by name into a variadic ...$args parameter, PHP collects them into an associative array where the keys are the argument names (strings) and values are the passed values. This is fundamentally different from positional variadic calls, where $args is an indexed array. Laravel exploits a similar mechanism in its Container::call() method: when resolving a closure's dependencies, it merges extra named parameters provided by the caller into the resolution context, allowing you to pass primitive values (like a route parameter $id) alongside container-resolved type-hinted dependencies.