Lesson 63 of 100
Functions15 min5 checksPHP 8.5

Pass by Reference in PHP Functions

Learn how the & marker on a parameter lets a function change the caller's variable, and how to use it on a local copy so no state leaks.

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.

Example
<?php
function addOne(int &$n): void { $n++; }
$a = 5;
addOne($a);
echo $a, "\n";
Output
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.

Example
<?php
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";
Output
["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:

  1. Copy the input into a local variable.
  2. Hand that local variable to a reference-taking helper.
  3. 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.

Example
<?php
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";
Output
{"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 $item afterwards. 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.

Before you start

Functions · Arrays · Types

You’ll practice
  • Modify a local copy through an explicit reference helper without leaking state
  • Implement and test a deterministic PHP 8.4 function
Your task

Make cap_bonus_scores() pass all 5 checks. Run your code as often as you like, then check your answer.

exercise.phpSaved in this browserPHP 8.5
CONSOLE & CHECKS● Ready

Ready when you are

Your output will appear here.
Make it work. Then make it pass.

PHP loads on your first run. Each check starts fresh. Your code stays in this browser; clearing site data removes your progress. A check or the task looks wrong? Report a problem with this exercise.