Re: foreach weirdness

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 12-03-23 11:16 AM, Arno Kuhl wrote:
The following snippet is copied from the php manual:
foreach ($arr as $key =>  $value) {
	echo "Key: $key; Value: $value<br />\n";
}

I've always used the foreach loop that way.
But recently I started hitting some really odd problems.

See this following example that illustrates the problem:
$array = array(0, 1, 2, 3, 4, 5, 6);
foreach ($array as $index=>$value) {
	if ( ($index+1)<  count($array) ) {
		$array[$index+1] += $value;
	}
	echo $value." ";
}
echo "<br />";
foreach ($array as $index=>$value) {
	echo $value." ";
}

You'd expect the output to be:
0 1 3 6 10 15 21
0 1 3 6 10 15 21

But it's actually:
0 1 2 3 4 5 6
0 1 3 5 7 9 11

This is what I would expect since the value is a copy. As such, one would expect it to be the value before you made modifications to the array.

If you assign the $value by reference in the first loop as someone pointed
out (and confirmed by the manual: "As of PHP 5, you can easily modify
array's elements by preceding $value with&. This will assign reference
instead of copying the value")

$array = array(0, 1, 2, 3, 4, 5, 6);
foreach ($array as $index=>&$value) {    //<- assign $value by reference
	if ( ($index+1)<  count($array) ) {
		$array[$index+1] += $value;
	}
	echo $value." ";
}
echo "<br />";
foreach ($array as $index=>$value) {
	echo $value." ";
}

it still does not produce the correct result:
0 1 3 6 10 15 21
0 1 3 6 10 15 15

This looks like a bug... the last row should be the same. What version of PHP are you using? Have you checked the online bug reports?

If I watch the $array in a debugger I see odd behaviour for the last element
$array[6] when stepping through the second foreach loop.
Just before entering the second loop $array[6] == 21 which is correct.
When I move to the next line (echo $value." ";)  $array[6] changes to 0 !!
As I step through the second loop $array[6] keeps on changing for each
iteration, with the following values:
0, 1, 3, 6, 10, 15, 15
And once I've left the second loop $array[6] is permanently changed from 21
to 15, even though there's no code in the second loop to change $array[6].
So what's going on here?

I confirm this by echoing $array[6] in each iteration in the second loop:
$array = array(0, 1, 2, 3, 4, 5, 6);
foreach ($array as $index=>&$value) {
	if ( ($index+1)<  count($array) ) {
		$array[$index+1] += $value;
	}
	echo $value." ";
}
echo "<br />";
foreach ($array as $index=>$value) {
	echo $array[6]." ";
}
echo "<br />";
echo $array[6];

the result is:
0 1 3 6 10 15 21
0 1 3 6 10 15 15
15

Note that $array[6] is still 15 even after completing the second foreach
loop.
If you break out of the second loop then $array[6] will be at whatever value
it was at the time you break out (ouch!)

If you assign the $value by reference in the second loop as well:
$array = array(0, 1, 2, 3, 4, 5, 6);
foreach ($array as $index=>&$value) {
	if ( ($index+1)<  count($array) ) {
		$array[$index+1] += $value;
	}
	echo $value." ";
}
echo "<br />";
foreach ($array as $index=>&$value) {    //<- assign $value by reference
	echo $array[6]." ";
}
echo "<br />";
echo $array[6];

you finally get the correct result:
0 1 3 6 10 15 21
21 21 21 21 21 21 21
21

You can test this with multiple foreach loops and get the same results. If
you modify the array in the first foreach loop, then use an assign $value by
reference in the next 9 foreach loops to get the correct values (without
modifying the array), and then in the 10th foreach loop you don't use an
assign $value by reference (without modifying the array), the array becomes
corrupted.

I sort of understand the need to assign the $value by reference in the first
loop, but why is it also required in every subsequent loop where the array
is not being modified? Especially since all the examples in the manual show
it's not needed? It would appear that once you've modified an array's
elements in a foreach loop you always have to assign $value by reference in
any subsequent foreach loop using that array. And if you don't, not only
will you get the wrong results but the array itself is actually altered,
even if there's no code in the loop to alter it. Is that correct or is it a
bug? At what stage can you start using the array in the "normal" way again?
That could create hair-pulling havoc for anyone maintaining code if they
haven't noticed that somewhere previously there was code that modified the
array in a foreach loop. Maybe the answer is to always assign $value by
reference in a foreach loop regardless of what you do in that loop, but I'm
not sure what the implications are.

Here's how you should do it (IMHO) to avoid all sorts of side effects, magic behaviour, and unnecessary complications:

<?php

    $array = array( 0, 1, 2, 3, 4, 5, 6 );
    foreach( array_keys( $array ) as $index )
    {
        if( ($index + 1) < count( $array ) )
        {
            $array[$index + 1] += $array[$index];
        }

        echo $array[$index].' ';
    }
    echo "\n";

    echo implode( ' ', $array )."\n";

?>

Cheers,
Rob.
--
E-Mail Disclaimer: Information contained in this message and any
attached documents is considered confidential and legally protected.
This message is intended solely for the addressee(s). Disclosure,
copying, and distribution are prohibited unless authorized.

--
PHP General Mailing List (http://www.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php



[Index of Archives]     [PHP Home]     [Apache Users]     [PHP on Windows]     [Kernel Newbies]     [PHP Install]     [PHP Classes]     [Pear]     [Postgresql]     [Postgresql PHP]     [PHP on Windows]     [PHP Database Programming]     [PHP SOAP]

  Powered by Linux