Named Arguments
Named arguments let a function or method call identify arguments by parameter name instead of only by position. PHP introduced them in PHP 8.0.
They can make calls with several optional or similar-looking values easier to read and can skip earlier defaulted parameters. They also make parameter names part of the practical public API, so use them deliberately.
Official reference: PHP function arguments.
Name Arguments At The Call Site
Given this function:
<?php
declare(strict_types=1);
function createThumbnail(
string $sourcePath,
string $targetPath,
int $width = 320,
int $height = 180,
bool $crop = true,
): string {
return "$sourcePath -> $targetPath {$width}x{$height} crop=" . ($crop ? 'yes' : 'no');
}
A named call can state the meaning of each value:
<?php
declare(strict_types=1);
$result = createThumbnail(
sourcePath: '/uploads/photo.jpg',
targetPath: '/cache/photo-small.jpg',
width: 640,
height: 360,
crop: false,
);
echo $result, PHP_EOL;
The labels are parameter names. They are not arbitrary descriptions. A misspelled or unknown name causes an error.
Named Arguments Can Change Order
Named arguments are matched by name, so their order at the call site does not need to follow the declaration:
<?php
declare(strict_types=1);
function formatMoney(int $pence, string $currency = 'GBP', bool $showCode = true): string
{
$amount = number_format($pence / 100, 2, '.', ',');
return $showCode ? "$currency $amount" : $amount;
}
echo formatMoney(showCode: false, pence: 1299), PHP_EOL;
// Prints:
// 12.99
Reordering can improve emphasis, but wildly different ordering across calls makes scanning harder. Follow declaration order unless changing it provides a clear benefit.
Skip Optional Parameters
A major use is selecting one later option without restating earlier defaults:
<?php
declare(strict_types=1);
function connect(
string $host,
int $port = 5432,
int $timeoutSeconds = 5,
bool $useTls = true,
): array {
return compact('host', 'port', 'timeoutSeconds', 'useTls');
}
$config = connect(host: 'db.internal', timeoutSeconds: 2);
print_r($config);
The default port and TLS choice remain in effect. A positional call would need to pass 5432 merely to reach the timeout argument.
Named arguments do not fix a function with too many responsibilities or options. A configuration object may be clearer when parameters keep growing, validation is complex, or combinations have rules.
Positional Arguments Must Come First
A call may use positional arguments followed by named arguments:
<?php
declare(strict_types=1);
function schedule(string $job, int $delaySeconds = 0, int $attempts = 3): string
{
return "$job delay=$delaySeconds attempts=$attempts";
}
echo schedule('SendInvoice', attempts: 5), PHP_EOL;
A positional argument cannot follow a named argument. Once the call starts naming arguments, remaining arguments must be named.
Avoid mixing styles when naming all arguments is clearer. A single obvious first positional value followed by named options can be readable.
Parameter Names Become API Surface
Before PHP 8, a library could often rename $timeout to $timeoutSeconds without affecting positional callers. Named callers make that rename a backward-compatibility break:
<?php
declare(strict_types=1);
function fetchPage(string $url, int $timeoutSeconds = 5): string
{
return "$url timeout=$timeoutSeconds";
}
// A caller may depend on the exact name:
echo fetchPage(url: 'https://example.test', timeoutSeconds: 2), PHP_EOL;
Library maintainers should choose stable, meaningful parameter names and include renames in compatibility review. Tests should include named calls for supported public APIs.
Application-private methods can be refactored more easily because all callers are controlled, but automated rename tools still need to update named call sites.
Names Should Communicate Units And Meaning
Named arguments amplify parameter quality. Prefer:
subtotalPence
ratePercent
timeoutSeconds
maximumAttempts
over vague names such as $value, $number, $time, or $flag.
A call retry(wait: 5) remains ambiguous. retry(delayMilliseconds: 5) communicates the unit.
Boolean arguments are often clearer when named:
<?php
declare(strict_types=1);
function exportReport(string $path, bool $includeHeader = true, bool $compress = false): string
{
return "$path header=" . ($includeHeader ? 'yes' : 'no')
. ' compress=' . ($compress ? 'yes' : 'no');
}
echo exportReport('/tmp/report.csv', compress: true), PHP_EOL;
Still avoid long runs of boolean switches. Separate methods, enums, or an options object may model the choices better.
Duplicate Assignment Is An Error
Do not provide one parameter both positionally and by name:
formatMoney(1299, pence: 1499)
The parameter has already been assigned. Similar errors can happen when unpacked arrays contain a name already supplied explicitly.
Construct dynamic argument arrays carefully and prefer direct typed calls when the call shape is known at development time.
Named Arguments And Variadics
Unknown named arguments can be collected by a variadic parameter:
<?php
declare(strict_types=1);
function buildContext(string $event, mixed ...$metadata): array
{
return ['event' => $event, 'metadata' => $metadata];
}
$context = buildContext(
event: 'OrderPlaced',
orderId: 42,
source: 'checkout',
);
print_r($context);
The variadic array retains string keys for named arguments. This can support metadata APIs, but it weakens discoverability and type checking. Do not use a variadic bag to hide a stable structured object.
Validate allowed keys and values when arbitrary metadata crosses a trust boundary.
Unpacking Uses Array Keys
The spread operator can supply arguments from an array. Integer keys act positionally; string keys act as names in modern PHP:
<?php
declare(strict_types=1);
function renderCard(string $title, string $theme = 'light', bool $compact = false): string
{
return "$title theme=$theme compact=" . ($compact ? 'yes' : 'no');
}
$options = [
'title' => 'Sales',
'compact' => true,
];
echo renderCard(...$options), PHP_EOL;
Unpacking external or loosely structured arrays can fail at runtime because of unknown names, missing required values, duplicates, or wrong types. Map configuration explicitly before calling a typed API.
An associative array now depends on parameter names. Renaming a parameter can break dynamic unpacking just as it breaks an explicit named call.
Do Not Forward Arbitrary Request Data
This is unsafe design:
serviceMethod(...$_POST)
Request keys could select parameters the user should not control, such as owner ID, role, status, or price. Named arguments do not provide authorization or validation.
Build an allowlisted command or DTO:
<?php
declare(strict_types=1);
function acceptedProfileInput(array $input): array
{
return [
'displayName' => $input['display_name'] ?? null,
'timezone' => $input['timezone'] ?? null,
];
}
Validate types and permissions before calling application behavior.
Named Arguments With Inheritance And Interfaces
Callers may use the parameter names declared by the concrete callable they invoke. Renaming parameters in an implementation while an interface or parent class communicates different names can create confusion and compatibility risk.
Keep parameter names consistent across interface methods, implementations, and overrides. Even where PHP's signature compatibility rules focus on types and order, named callers can depend on names.
Static analysis and coding standards can help enforce consistency.
Internal Functions Need Care
Named arguments work with many internal PHP functions, but relying on parameter names still creates version and compatibility considerations. Check the manual for the supported PHP versions and use the canonical names.
Named calls can improve functions with several options, but familiar positional calls remain reasonable for simple, stable signatures:
<?php
declare(strict_types=1);
$amount = number_format(
num: 1234.5,
decimals: 2,
decimal_separator: '.',
thousands_separator: ',',
);
echo $amount, PHP_EOL;
For a library supporting several PHP versions, test named calls on every supported version.
Attributes Also Use Named Arguments
Attribute construction can use named arguments because attribute instances are created through constructors:
<?php
declare(strict_types=1);
#[Attribute(Attribute::TARGET_METHOD)]
final readonly class Route
{
public function __construct(
public string $path,
public string $method = 'GET',
) {
}
}
final class ProductController
{
#[Route(path: '/products', method: 'GET')]
public function index(): void
{
}
}
This makes attribute metadata readable, while also making constructor parameter names part of the metadata API.
Named Arguments Are Not Named Parameters In Declarations
All function parameters already have names. The feature changes how callers bind arguments. Do not describe it as changing runtime type enforcement or function overloading.
PHP still selects one function signature. Named arguments cannot omit a required parameter, supply an unknown non-variadic parameter, or bypass type declarations.
When Positional Calls Are Better
Use positional arguments when:
- there are one or two obvious inputs;
- established mathematical or collection APIs are clearer conventionally;
- the function is private and the meaning is obvious;
- names add noise without information.
<?php
declare(strict_types=1);
function distance(int $start, int $end): int
{
return abs($end - $start);
}
echo distance(4, 10), PHP_EOL;
Naming both values may help in some domains, but it is not automatically superior.
When An Object Is Better
A function with twelve named options remains hard to understand. An immutable configuration object can centralize defaults, validation, and documentation:
new ExportOptions(format, compression, delimiter, encoding, includeHeader)
A builder or named constructor can model valid combinations. Use named arguments to improve calls, not to avoid designing a meaningful type.
Compatibility
Named arguments require PHP 8.0 or later at the call site. Older PHP versions cannot parse the syntax. They cannot be polyfilled.
A library can support PHP 7 and PHP 8 callers because positional calls remain valid, but its own distributed source cannot contain named-call syntax if PHP 7 must parse it.
Set an accurate Composer PHP constraint and run CI on all supported versions. Treat public parameter renames as compatibility changes once named calling is supported.
Refactor And Test Safely
When introducing named calls:
- confirm the project baseline is PHP 8.0 or later;
- use canonical parameter names;
- preserve required arguments and types;
- do not duplicate positional and named assignments;
- test skipped defaults;
- inspect unpacked associative arrays;
- update callers during parameter renames;
- test interface and override consistency;
- avoid forwarding raw external arrays.
Tests should call public APIs both positionally and by supported names when compatibility matters.
Review Checklist
For each named call, ask:
- Does naming add useful meaning?
- Are parameter names stable public API?
- Are units and boolean meanings clear?
- Do positional arguments come before named ones?
- Is a value assigned more than once?
- Can unpacked keys be trusted and validated?
- Would a DTO or options object better represent the configuration?
- Are inherited parameter names consistent?
- Is PHP 8.0 or later guaranteed?
What You Should Be Able To Do
After this lesson, you should be able to call functions with named arguments, reorder them, skip optional defaults, combine positional then named arguments, and unpack associative arrays deliberately.
You should also understand why parameter names become compatibility surface, how variadics collect unknown named values, why raw request arrays must not be forwarded, and when a configuration object is more maintainable than a long named-argument list.
Practice
Call With Named Arguments
Create a function for exporting a report with required path and optional format, compression, delimiter, and header settings. Use a named call that changes compression and delimiter while retaining the other defaults.
Explain why the parameter names should include meaning and units where applicable.
Show solution
<?php
declare(strict_types=1);
function exportReport(
string $path,
string $format = 'csv',
bool $compress = false,
string $delimiter = ',',
bool $includeHeader = true,
): array {
return compact('path', 'format', 'compress', 'delimiter', 'includeHeader');
}
$options = exportReport(
path: '/tmp/orders.csv',
compress: true,
delimiter: ';',
);
Names make the booleans and delimiter readable while defaults remain centralized in the declaration.
Review A Parameter Rename
A public package changes timeout to timeoutSeconds without changing order or type. Explain why this can break PHP 8 callers.
Design a compatibility review and migration approach, including tests and release communication.
Show solution
Search known callers, add named-call contract tests, document the change, and release it according to the package's compatibility policy, normally in a major version. When feasible, avoid the rename or introduce a new method/options object rather than trying to support two names in one fixed signature.
Unpack Options Safely
An application receives a configuration array and calls connect(...$config). Design an allowlist mapper that rejects unknown keys and returns only host, port, timeoutSeconds, and useTls with validated types.
Explain why directly unpacking request or configuration data is fragile.
Show solution
Compare input keys with the explicit allowlist and reject extras. Require a non-empty string host, positive integer port and timeout, and boolean TLS value. Build a new array with canonical parameter names before unpacking.
Direct unpacking couples external key names to PHP parameter names, can assign sensitive options, and fails at runtime for unknown, duplicate, missing, or incorrectly typed values. Explicit mapping creates a validation and authorization boundary.