PHP Foreach Pass by Reference: Last Element Duplicating (Bug)

PHP Foreach Pass by Reference: Last Element Duplicating? (Bug?)

After the first foreach loop, $item is still a reference to some value which is also being used by $arr[2]. So each foreach call in the second loop, which does not call by reference, replaces that value, and thus $arr[2], with the new value.

So loop 1, the value and $arr[2] become $arr[0], which is 'foo'.

Loop 2, the value and $arr[2] become $arr[1], which is 'bar'.

Loop 3, the value and $arr[2] become $arr[2], which is 'bar' (because of loop 2).

The value 'baz' is actually lost at the first call of the second foreach loop.

Debugging the Output

For each iteration of the loop, we'll echo the value of $item as well as recursively print the array $arr.

When the first loop is run through, we see this output:

foo
Array ( [0] => foo [1] => bar [2] => baz )

bar
Array ( [0] => foo [1] => bar [2] => baz )

baz
Array ( [0] => foo [1] => bar [2] => baz )

At the end of the loop, $item is still pointing to the same place as $arr[2].

When the second loop is run through, we see this output:

foo
Array ( [0] => foo [1] => bar [2] => foo )

bar
Array ( [0] => foo [1] => bar [2] => bar )

bar
Array ( [0] => foo [1] => bar [2] => bar )

You'll notice how each time array put a new value into $item, it also updated $arr[3] with that same value, since they are both still pointing to the same location. When the loop gets to the third value of the array, it will contain the value bar because it was just set by the previous iteration of that loop.

Is it a bug?

No. This is the behavior of a referenced item, and not a bug. It would be similar to running something like:

for ($i = 0; $i < count($arr); $i++) { $item = $arr[$i]; }

A foreach loop isn't special in nature in which it can ignore referenced items. It's simply setting that variable to the new value each time like you would outside of a loop.

Why php iteration by reference returns a duplicate last record?

I'll guess that you're reusing &$item here and that you're stumbling across a behavior which has been reported as bug a thousand times but is the correct behavior of references, which is why the manual advises:

Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().

foreach($arrayOfJsonMods as &$item)
{
//TODO memcached votes
}
unset($item);

See https://bugs.php.net/bug.php?id=29992

foreach loop - php - weird behavior

It's PHP's behavior of a referenced item.

Reason : I think this guy can explain better than me.

Just a little memo.

Alternatively, to fix this, you can add & to $age at your third foreach:

foreach( $employee_age as $name => &$age){
echo "Name: $name, Age: $age <br />";
}

PHP Pass by reference in foreach

Because on the second loop, $v is still a reference to the last array item, so it's overwritten each time.

You can see it like that:

$a = array ('zero','one','two', 'three');

foreach ($a as &$v) {

}

foreach ($a as $v) {
echo $v.'-'.$a[3].PHP_EOL;
}

As you can see, the last array item takes the current loop value: 'zero', 'one', 'two', and then it's just 'two'... : )

foreach and reference

From the docs:

Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().

So in your case:

<?php

$foo = array('one', 'two', 'three');

foreach ($foo as &$bar)
{
// no-op
}

var_dump($foo);

unset($bar);

foreach ($foo as $bar)
{
// no-op
}

var_dump($foo);
?>

PHP pass by reference bug?

Not a bug, if you want to reuse variable previously used as reference, you need to do this:

unset($var);

Specifically, adding this one line after your first loop:

foreach($array as &$reference){
if($reference == "one") $reference = "one_changed";
}
unset($reference); // <--- this

Resolves all problems:

Array
(
[0] => one
[1] => two
[2] => three
)
Array
(
[0] => one_changed
[1] => two
[2] => three
)
====changed them but did not push onto array====<br>====changed also====Array
(
[0] => one_changed
[1] => two
[2] => three
)
====changed also====Array
(
[0] => one_changed
[1] => two
[2] => three
)

And one more thing. Array elements also can be references, e.g.:

$a = 1;

$c=[];
$c[0] =& $a;
$c[1] =& $a;
print_r($c);

$a=2;
print_r($c);

That will output:

Array
(
[0] => 1
[1] => 1
)
Array
(
[0] => 2
[1] => 2
)

So, to explain what exactly going on in your code... I'd rather pass. You make a referenced loop variable, then you use it as non-referenced variable in another loop and push it as a value in other array... Screw it, I give up.

Strange behavior of foreach when using reference: foreach ($a as &$v) { ... }

This is well-documented PHP behaviour
See the warning on the foreach page of php.net

Warning

Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().

$a = array('a', 'b', 'c', 'd');

foreach ($a as &$v) { }
unset($v);
foreach ($a as $v) { }

print_r($a);

EDIT

Attempt at a step-by-step guide to what is actually happening here

$a = array('a', 'b', 'c', 'd');
foreach ($a as &$v) { } // 1st iteration $v is a reference to $a[0] ('a')
foreach ($a as &$v) { } // 2nd iteration $v is a reference to $a[1] ('b')
foreach ($a as &$v) { } // 3rd iteration $v is a reference to $a[2] ('c')
foreach ($a as &$v) { } // 4th iteration $v is a reference to $a[3] ('d')

// At the end of the foreach loop,
// $v is still a reference to $a[3] ('d')

foreach ($a as $v) { } // 1st iteration $v (still a reference to $a[3])
// is set to a value of $a[0] ('a').
// Because it is a reference to $a[3],
// it sets $a[3] to 'a'.
foreach ($a as $v) { } // 2nd iteration $v (still a reference to $a[3])
// is set to a value of $a[1] ('b').
// Because it is a reference to $a[3],
// it sets $a[3] to 'b'.
foreach ($a as $v) { } // 3rd iteration $v (still a reference to $a[3])
// is set to a value of $a[2] ('c').
// Because it is a reference to $a[3],
// it sets $a[3] to 'c'.
foreach ($a as $v) { } // 4th iteration $v (still a reference to $a[3])
// is set to a value of $a[3] ('c' since
// the last iteration).
// Because it is a reference to $a[3],
// it sets $a[3] to 'c'.


Related Topics



Leave a reply



Submit