For me this is unexpected, but I’m sure someone can explain why in this block of code the final item to be printed is ‘four’, so the sequence would be:
onetwothreefourfiveonetwothreefourfour
The first sequence passes by reference, this is a broken down example of a function I am trying to debug and came across. I know how to fix the issue / refactor it but now curious why PHP works this way?
<?php
$items = ['one', 'two', 'three', 'four', 'five'];
foreach ($items as &$item) {
echo $item;
}
foreach ($items as $item) {
echo $item;
}
3
Answers
You need to delete the reference before using the same variable name again.
Demo: https://3v4l.org/qQBXP
Also, there is no need for using a reference until you want to change the items.
Read more about in the manual.
Reason, why the last is
four
:When the second loop starts, it reuses the
$item
variable. But since it is a reference to the last element of the array, the assignment$item = 'five'
changes the last element of the array tofour
, which is the last value$item
had in the second loop.In other words: The pointer is still kept at the last element and it is overwritten with each iteration. In the 2nd loop the last will be overwritten with
one
. In the next iteration withtwo
and so on.anyber‘s demo at https://3v4l.org/b2eIb (from the comments) is very helpful.
I will reproduce the code and output here, for ease:
Code
Output
Explanation
Each time the loop runs,
$item
is assigned a value from the array, but because it’s passed by reference, it’s linked to that entry in the array (hence the&
s in the dumped output).The next run of the loop reassigns the reference to the next array entry. This all works fine for the first loop and doesn’t interefere with anything.
But notice at the end of the first loop, the last entry in the array still retains its reference to
$item
. Then when you start looping again, we can observe that the last entry in the array is, each time, changed into the value of the current$item
value.In the second loop, the reference no longer passes from entry to entry this time, but remains on the last entry in the array. This is because in the second loop
$item
is not being passed by reference – that’s the important difference.Hence by the time you get to the last iteration of the second loop, the 5th entry has the value "four", and since that’s also the current index of the loop, there’s nothing else to change it to.
Resolution
If we were to pass
$item
by reference in both loops (https://3v4l.org/DgYQ0) or neither (https://3v4l.org/cXnIR) we would get the output you were expecting, withfive
as the last output value.A third way to resolve it would be to leave both loops as they are, but
unset
the$item
value between each one (https://3v4l.org/5Xa7I) as suggested in this answer.The last and perhaps most obvious solution is simply to use a different variable name in the second loop (https://3v4l.org/JjiEu) – which is always a good principle anyway in a language like PHP, where loop variables stay in scope after the loop ends.
This behaviour is easiest to understand if you "unroll" the two
foreach
loops, replacing each iteration of theas &$item
with assign-by-reference, and each iteration of theas $item
as normal assignment:Becomes something like this:
The key thing to notice is the last assignment from the first loop, and the first assignment in the next:
This does an assignment-by-reference, binding
$item
and$items[4]
together; then, we assign by value to$item
, which is equivalent to assigning to$items[4]
. In other words, we’re doing this:Since we never break the link to
$items[4]
, every assignment to$items
continues to point there, as though we had this:The solution meanwhile, is to have a very simple rule: whenever you
foreach
by reference, callunset
afterwards: