Pass by Value vs Reference
When you call a PHP function with an argument, what actually gets handed to the function depends on how the parameter is declared. The default behavior is pass by value: the function receives an independent copy of a scalar or array, and changes made inside the function have no effect on the caller's original variable. PHP also supports pass by reference, using an ampersand (&) in the function signature, which lets a function reach back and modify the caller's variable directly. Objects add a third wrinkle worth understanding on their own, because they behave differently from both plain scalars and reference parameters.
Pass by value: the default for scalars and arrays
Unless you say otherwise, every argument — a number, a string, a boolean, or an entire array — is copied when it is passed into a function. The function works on its own private copy; whatever it does to that copy is invisible to the caller once the function returns.
Modifying the parameter does not touch the caller's array
<?php
function addItem(array $list)
{
$list[] = "new item";
echo "Inside function: " . implode(", ", $list) . "\n";
}
$groceries = ["milk", "eggs"];
addItem($groceries);
echo "Outside function: " . implode(", ", $groceries);Inside function: milk, eggs, new item Outside function: milk, eggs
$groceries in the caller is completely unaffected by what addItem() did to its own $list copy. This is true even though arrays can be large — PHP uses a copy-on-write optimization internally so the copy is cheap until something actually modifies it, but from your code's point of view it behaves exactly as if a full copy were made immediately.
Pass by reference with &$param
Prefixing a parameter with & in the function signature switches that specific parameter to pass by reference. Instead of a copy, the function receives a direct alias to the caller's variable, so any change made to the parameter inside the function is visible outside it too, once the function returns.
&$list lets the function modify the caller's array in place
<?php
function addItemByRef(array &$list)
{
$list[] = "new item";
}
$groceries = ["milk", "eggs"];
addItemByRef($groceries);
echo implode(", ", $groceries);milk, eggs, new item
This is exactly the mechanism several built-in PHP functions rely on, such as sort(), array_push() (in a different form), and preg_match()'s $matches output parameter — they all declare their array parameter by reference so they can update it in place rather than requiring you to reassign a return value.
sort() is a real-world example of pass by reference
<?php $scores = [42, 7, 19, 3]; sort($scores); // sort() takes its argument by reference internally print_r($scores);
Array
(
[0] => 3
[1] => 7
[2] => 19
[3] => 42
)A function that empties an array in place
Reference parameters are especially handy for "act in place" helper functions, where you want the caller's variable itself to end up changed rather than getting a new value back that has to be reassigned.
Draining a queue in place
<?php
function drainQueue(array &$queue): array
{
$removed = $queue;
$queue = [];
return $removed;
}
$tasks = ["email", "invoice", "backup"];
$processed = drainQueue($tasks);
echo "Processed: " . implode(", ", $processed) . "\n";
echo "Remaining: " . (empty($tasks) ? "(none)" : implode(", ", $tasks));Processed: email, invoice, backup Remaining: (none)
Objects: handles, not true references
Objects are often described as "passed by reference" in PHP, but that phrase is technically imprecise and worth untangling. A variable that holds an object does not hold the object itself — it holds a small internal handle (an identifier PHP uses internally to find the object). When you pass that variable into a function without an &, PHP copies the handle, not the object. Both the caller's variable and the function's parameter now point at the exact same underlying object, so mutating a property through either one affects the object both variables refer to.
Mutating a property through the copied handle
<?php
class Counter
{
public int $value = 0;
}
function increment(Counter $counter)
{
$counter->value++; // mutates the shared object
}
$c = new Counter();
increment($c);
increment($c);
echo $c->value;2
Now compare that to reassigning the parameter itself inside the function. Reassignment changes what the local parameter variable points to, but it does not reach back and change what the caller's variable points to — because the handle was copied by value, just like a scalar would be.
Reassigning the parameter does not affect the caller's variable
<?php
class Counter
{
public int $value = 0;
}
function replace(Counter $counter)
{
$counter = new Counter(); // points the local copy at a brand new object
$counter->value = 100; // only affects the new object
}
$original = new Counter();
$original->value = 5;
replace($original);
echo $original->value; // still 5 - $original was never touched5
The distinction matters: increment() changed the object everyone can see because it reached through the handle to modify a property. replace() only changed what its own local $counter variable pointed to — it never touched $original's handle at all.
Passing an object by reference with &
If you genuinely want a function to be able to reassign the caller's variable to a different object entirely — not just mutate the existing one — you still need the & reference operator, exactly like with scalars and arrays.
With &, reassignment IS visible to the caller
<?php
class Counter
{
public int $value = 0;
}
function replaceByRef(Counter &$counter)
{
$counter = new Counter();
$counter->value = 100;
}
$original = new Counter();
$original->value = 5;
replaceByRef($original);
echo $original->value; // now 100 - $original itself was reassigned100
Scalars (int, float, string, bool) and arrays are always copied on assignment and on being passed as arguments, unless the parameter uses
&.A variable holding an object stores a handle to that object, not the object itself; copying the variable copies the handle, not the object.
Mutating a property through any variable/parameter that shares a handle affects the one underlying object everywhere it is referenced.
Reassigning a parameter to a brand-new object only changes what that parameter points to, unless the parameter was declared with
&.Reference parameters (&$param) work the same way for objects as for scalars and arrays: they let the function replace what the caller's variable points to, not just mutate it.