01 · THE IDEAREAD → TRY → LEARN
The problem: functions normally get copies
By default PHP passes arguments by value. The function receives its own copy of the data, so assigning to the parameter changes only that copy. This is usually what you want, because a function cannot surprise its caller by rewriting variables.
Sometimes you do want a function to change the caller's variable. For that, PHP lets you pass by reference.
The syntax
Put an ampersand before the parameter name in the function definition. The call site stays unchanged. The manual stresses this: there is no reference sign on a function call, and the definition alone is enough.
function addOne(int &$n): void { $n++; }
$a = 5;
addOne($a);
echo $a, "\n";6
Inside addOne, $n is another name for the caller's $a. Incrementing it changes $a itself, so the caller sees 6.
What may be passed by reference
A by-reference parameter needs something it can point at, which in practice means a variable. A literal such as foo(5) is a fatal error. Passing the result of an ordinary function call or a new expression produces a notice that only variables should be passed by reference. Treat those forms as invalid. Assign the value to a variable first, then pass the variable.
Value versus reference with arrays
Arrays make the difference easy to see. Compare a by-value helper with a by-reference helper.
function appendByValue(array $items): void { $items[] = 'x'; }
function appendByReference(array &$items): void { $items[] = 'x'; }
$list = ['a'];
appendByValue($list);
echo json_encode($list, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
appendByReference($list);
echo json_encode($list, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";["a"] ["a","x"]
The first call changes a copy that is thrown away. The second call changes $list.
Keeping state from leaking
References are powerful but make data flow harder to follow. A function that edits its argument in place has a hidden side effect. A tidy approach is to confine the mutation to a local copy:
- Copy the input into a local variable.
- Hand that local variable to a reference-taking helper.
- Return the local variable.
The caller's data is never touched, yet you still get the convenience of in-place editing inside the helper. A closure with an & parameter works well as a private helper, because it exists only for that call.
function withTotal(array $data): array {
$add = function (array &$d): void {
$d['total'] = array_sum($d['values']);
};
$add($data);
return $data;
}
$original = ['values' => [2, 3]];
$result = withTotal($original);
echo json_encode($original, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"values":[2,3]}
{"values":[2,3],"total":5}
Here $data is already a copy because withTotal takes it by value. The closure edits that copy through a reference. The original array has no total key, and the returned array does.
Worked reasoning
Trace withTotal. The caller's $original is passed by value, so $data starts as an independent array. The call $add($data) binds $d to $data. Writing $d['total'] therefore writes into $data. When the function returns, $data goes out of scope after its value is returned, and nothing links the result back to $original.
Common mistakes
- Forgetting the
&in the definition. The helper then edits a copy and the change silently vanishes. - Putting
&at the call site. That is not how PHP passes by reference. - Passing a literal or a call result where a variable is required.
- Using a reference when returning a new value would be clearer. Prefer returning values unless in-place change is the point.
- Using
foreach ($items as &$item)and then reusing$itemafterwards. The reference lingers after the loop, so unset it or index the array directly instead.
Exercise contract
Write cap_bonus_scores(array $scores, int $bonus, int $cap): array. It must add $bonus to every score and limit each result to at most $cap. Do the update inside a closure that takes the array by reference, apply it to a local copy, and return that copy. Keys and order must be kept. The input is a list of integers, and an empty list returns an empty list.
Recap
Use & on a parameter in the definition to let a function change the caller's variable. Pass only variables. Because by-reference edits are a side effect, apply them to a local copy and return it, so your function remains predictable and easy to test.
Functions · Arrays · Types
- Modify a local copy through an explicit reference helper without leaking state
- Implement and test a deterministic PHP 8.4 function