PHP Default Arguments
A default argument (or default parameter value) lets you give a function parameter a fallback value right inside its declaration. If the caller leaves that argument out, PHP quietly supplies the default; if the caller passes a value, PHP uses that instead. This is one of the simplest ways to make functions flexible without writing multiple overloaded versions or manually checking how many arguments were passed.
Default arguments show up everywhere in real PHP code: configuration-style functions, formatting helpers, query builders, and constructors that accept optional settings. Understanding exactly how and when PHP resolves them — and where the rules bite beginners — will save you from some confusing bugs later.
Overview / How it works
When PHP compiles a function, the Zend engine stores each parameter’s default-value expression alongside the function’s compiled bytecode (its op array). The default value itself must be a constant expression — something PHP can resolve without running arbitrary code: a literal (42, "text", true), a defined constant, an enum case, a simple arithmetic expression on constants, an array made of constant values, or (since PHP 8.1) a new SomeClass() expression. It cannot be a variable, a function call result, or another parameter’s value.
At call time, the engine compares how many arguments were actually supplied to how many parameters were declared. For every parameter the caller didn’t provide — whether that gap is because they passed fewer positional arguments or skipped one using a named argument — PHP evaluates that parameter’s stored default expression fresh and assigns the result to the local variable before the function body runs.
That word “fresh” matters. Unlike some dynamic languages where a mutable default (like an array or object) is created once and then silently shared and mutated across every call, PHP re-evaluates the default expression on every single invocation. There is no shared state leaking between calls purely because of a default value — each call gets its own independent copy.
Default arguments also interact with PHP’s type system: if a parameter has a type declaration, the default value must be compatible with that type (or the parameter must be nullable if the default is null). This is why you’ll often see patterns like ?float $tax = null instead of trying to compute a real numeric default from another parameter.
Syntax
<?php
function functionName(string $requiredParam, string $optionalParam = "default value"): string
{
return "$requiredParam - $optionalParam";
}
| Part | Meaning |
|---|---|
string $requiredParam |
A normal parameter with no default — the caller must supply it. |
string $optionalParam = "default value" |
A parameter with a default; omitting it at the call site uses "default value". |
: string |
The return type declaration (optional but recommended). |
Key rules
- All parameters after the first one with a default must also have defaults — unless you rely on named arguments to fill gaps (see Best Practices).
- A default value must be a constant expression: literals, constants, enum cases, arrays of constants, or
new(PHP 8.1+). It cannot reference another parameter or call a function. - Variadic parameters (
...$args) cannot have a default value and must be the last parameter. - A typed parameter can only default to
nullif its type is nullable (e.g.?float).
Examples
Example 1: A simple greeting function
<?php
function greet(string $name, string $greeting = "Hello"): string {
return "$greeting, $name!";
}
echo greet("Alice") . PHP_EOL;
echo greet("Bob", "Hi") . PHP_EOL;
Output:
Hello, Alice!
Hi, Bob!
The first call omits the second argument entirely, so PHP substitutes "Hello". The second call overrides it with "Hi".
Example 2: Multiple defaults with named arguments
<?php
function createUser(string $username, string $role = "member", bool $active = true): string {
$status = $active ? "active" : "inactive";
return "User: $username, Role: $role, Status: $status";
}
echo createUser("jdoe") . PHP_EOL;
echo createUser("asmith", "admin") . PHP_EOL;
echo createUser("bwayne", active: false) . PHP_EOL;
Output:
User: jdoe, Role: member, Status: active
User: asmith, Role: admin, Status: active
User: bwayne, Role: member, Status: inactive
The third call is the interesting one: it supplies $username positionally, then jumps straight to active by name, skipping $role entirely so it falls back to its default of "member". Without named arguments you’d have had to pass "member" explicitly just to reach the third parameter.
Example 3: Enum as a default value
<?php
enum LogLevel: string {
case Info = "INFO";
case Warning = "WARNING";
case Error = "ERROR";
}
function logMessage(string $message, LogLevel $level = LogLevel::Info): string {
return "[{$level->value}] {$message}";
}
echo logMessage("Server started") . PHP_EOL;
echo logMessage("Disk space low", LogLevel::Warning) . PHP_EOL;
echo logMessage("Database connection failed", LogLevel::Error) . PHP_EOL;
Output:
[INFO] Server started
[WARNING] Disk space low
[ERROR] Database connection failed
Since PHP 8.1, enum cases are valid constant expressions, so they can be used directly as default parameter values — a much safer alternative to defaulting to a bare string like "info", since the type system now enforces that only valid LogLevel values can ever be passed.
How it works step by step / Under the hood
- The parser reads the function declaration and stores each parameter’s default expression as part of the compiled function (its op array), along with flags marking which parameters are optional.
- When the function is called, the engine counts how many arguments were actually supplied (positionally and by name combined).
- For every declared parameter that wasn’t supplied, the engine runs that parameter’s stored default-value instructions and binds the result to the parameter’s local variable slot, before the function body executes.
- Because this happens on every call, a default like
[]ornew DateTime()is a brand-new value each time — it is never shared or mutated across separate calls. - Named arguments are resolved first by matching names to parameter positions; any parameter left unmatched after that step (because it wasn’t passed positionally or by name) receives its default the same way.
- You can inspect all of this via Reflection, which is useful for frameworks, DI containers, and debugging tools that need to discover a parameter’s default at runtime.
<?php
function ship(string $item, string $method = "standard"): string {
return "$item via $method";
}
$reflection = new ReflectionFunction('ship');
$param = $reflection->getParameters()[1];
echo $param->isOptional() ? "optional" : "required";
echo PHP_EOL;
echo $param->getDefaultValue() . PHP_EOL;
Output:
optional
standard
Common Mistakes
Mistake 1: Referencing another parameter in a default value
It’s tempting to think a default can be computed from an earlier parameter, but default values must be constant expressions — they cannot see other parameters at all. This fails to compile:
function calculateTotal($price, $tax = $price * 0.1) {
return $price + $tax;
}
PHP rejects $price inside the default expression with a fatal compile-time error, because $tax‘s default is evaluated in a context where $price doesn’t exist yet as a constant. The fix is to default the second parameter to null and compute the real value inside the function body:
<?php
function calculateTotal(float $price, ?float $tax = null): float {
$tax ??= $price * 0.1;
return $price + $tax;
}
echo calculateTotal(100) . PHP_EOL;
echo calculateTotal(100, 5) . PHP_EOL;
Output:
110
105
The null-coalescing assignment operator ??= only overwrites $tax when it’s still null, so an explicitly-passed value is respected.
Mistake 2: Putting an optional parameter before a required one
PHP allows a required parameter to follow an optional one, but it’s deprecated and confusing — the “optional” parameter is effectively forced to be supplied whenever you call positionally:
<?php
function createOrder($discount = 0, $customerName) {
return "$customerName gets $discount% off";
}
echo createOrder(10, "Maria") . PHP_EOL;
Output:
Maria gets 10% off
This runs, but PHP 8.1+ emits a deprecation notice at declaration time, and there’s no way to call createOrder for just "Maria" without also specifying the discount. Reorder the parameters so required ones come first:
<?php
function createOrder($customerName, $discount = 0) {
return "$customerName gets $discount% off";
}
echo createOrder("Maria", 10) . PHP_EOL;
echo createOrder("Liam") . PHP_EOL;
Output:
Maria gets 10% off
Liam gets 0% off
Best Practices
- Declare required parameters first and optional (defaulted) ones last, so positional calls stay unambiguous.
- Default to
null(with a nullable type) when the real default can’t be expressed as a constant, then resolve it with??=or anifcheck inside the function. - Use named arguments when you need to skip over middle parameters and only override a later one, instead of restating every default in order.
- Prefer enum cases over raw strings or integers for defaults representing a fixed set of options — it gives you type safety for free.
- Keep the number of defaulted parameters small (roughly two or three); beyond that, consider an options object, a DTO, or constructor property promotion instead.
- Document non-obvious defaults in a docblock even if the signature already shows the literal value, especially when the default encodes a business rule.
Practice Exercises
Exercise 1
Write a function formatPrice(float $amount, string $currency = "USD", int $decimals = 2): string that returns the amount formatted with number_format followed by the currency code. Call it three times: once with only the amount, once overriding the currency, and once overriding all three arguments.
Exercise 2
Write a function connect(string $host, int $port = 5432, bool $ssl = true): string that returns a summary string. Call it using named arguments so you can disable $ssl without specifying $port.
Exercise 3
Define an enum Size with cases Small, Medium, and Large. Write a function orderDrink(string $name, Size $size = Size::Medium): string that returns a description of the order, and call it once relying on the default and once overriding it.
Summary
- A default argument supplies a fallback value used only when the caller omits that parameter.
- Default values must be constant expressions: literals, constants, enum cases, arrays of constants, or
new(PHP 8.1+) — never another parameter or a function call. - Defaults are re-evaluated on every function call, so there’s no shared-mutable-default trap across invocations.
- Parameters with defaults normally come after required ones; PHP allows the reverse order but deprecates it because it forces the “optional” value to be passed anyway.
- Named arguments let you skip over defaulted parameters to override only the ones you care about.
- Prefer
nulldefaults plus in-body resolution when the true default can’t be a constant expression.
