I’ve used both methods quite a bit but I recently wondered why I hadn’t standardized on one or the other, or if there was a best practice and what the reasons behind it are.
While the returning references page at PHP states quite clearly, with respect to returning references:
Only return references when you have a valid technical reason to do so.
…the passing by reference page isn’t as clear when talking about parameters and return methods.
Consider the following two examples:
Referenced return
class foo { public static function bar(&$val) { $val++; } } $input = 0; foo::bar($input); echo $input; // outputs 1
Explicit return
class foo { public static function bar($val) { $val++; return $val; } } $input = 0; $input = foo::bar($input); echo $input; // outputs 1
Both methods perform the same task and return the same value, but they accomplish it in only slightly different ways. I know that this scenario (multiple solutions to same task) is common in PHP, but my question is…
Is one of the above two methods preferred over the other and if so why?
I should point out that I did see other questions like this one: What’s the difference between passing by reference vs. passing by value? but they deal with effects and examples, not why one is preferred over the other.
EDIT: To save any others the hassle of reading through the long discussion regarding the merits of the question…
I was hoping for something specific when taking into consideration the overhead or optimizations inherent in the underlying code whether that be PHP or C, and how that effect would translate to performance after being converted to OpCode and put in the context of potentially thousands of app servers. Small optimizations can have a big effect on the bottom line at the end of the month.
I didn’t expect the reluctance, but my guess is that at first glance there isn’t a difference when you’re considering basic programming requirements. Ours is a different circumstance and so I should have been even more specific up front.
If anyone else cares to weigh in, I’m all ears.
Advertisement
Answer
In regards to references in PHP, these are some issues to be aware of:
PHP uses copy-on-write to handle an assignment, and so
$b = $a
will not write $b to a different variable container until there is a change that brings the two variables to differ. One type of change that can occur is that a reference to $b is made by another variable such as$c = &$b
(in order for it to be a reference, it now has to copy $b). In C++ a reference does not have its own memory address, and is considered an alias that is-the-object-itself (even though in certain circumstances you can still have dangling references). In PHP, references “… are not like C pointers [pointers – not C++ references]; for instance, you cannot perform pointer arithmetic using them, they are not actual memory addresses, and so on.” Given the above information, we see that copying a variable does not take extra memory until a change is made. References in PHP, in some circumstances will add to memory. This happens when$b = $a
and$c = &$b
(unlike$c = &$a
), since even though $b has not changed from $a, it needs its own reference tracking mechanism within a separate zval variable container for $b, and therefore triggers a write for $b.Differences between current and legacy versions of PHP can lead to different handling: “In PHP 4 objects are treated like other variables so when using them as function parameters or doing assignments they are copied. In PHP 5 they are always passed by reference.”
Languages, human or computer, normally tend toward simplification where possible (unless there is a benefit for complexity that outweighs simplicity):
- It is more difficult to track referenced changes coming out of a function / method (since there is no way, without looking at the function / method, to see which parameters are passing out as referenced).
- It is more difficult to keep track of changes involving underlying references.
- Core functionality sometimes handles differently for references.
- For instance:
$b = &a; unset($b);
does not unset $a, but is used to disassociate $b from $a (in this case $b is unset, $a retains its value). - For instance: array_filter() does not work with passed references.
- This does not work:
$a=array(0,'', -1); $b = &a; print_r(array_filter($b));
- This does not work:
- For instance:
Unexpected Results:
- Code like the following does not work as you would expect:
$array1 = array(1,2); $x = &$array1[1]; // Unused reference $array2 = $array1; // reference now also applies to $array2 ! $array2[1]=22; // (changing [0] will not affect $array1) print_r($array1); Produces: Array ( [0] => 1 [1] => 22 // var_dump() will show the & here )