Edit report at http://bugs.php.net/bug.php?id=29992&edit=1
ID: 29992 Comment by: looris at gmail dot com Reported by: fletch at pobox dot com Summary: foreach by reference corrupts the array Status: Bogus Type: Bug Package: Scripting Engine problem Operating System: linux PHP Version: 5.0.1 New Comment: This *IS* a bug, since no one would EVER expect this kind of behaviour. And no one "might use this for some weird reason", believe me. "foreach" means "do stuff FOR EACH element of an array". It does not mean, to any sane person, "do stuff for each element of an array except the last one, and twice for the one before the last one". Your stubbornness in stating this is intentional is quite frightening. Previous Comments: ------------------------------------------------------------------------ [2006-08-07 14:18:11] kou...@php.net Although there's a user note in the manual, still this problem is not described in the documentation itself. Anyway, I believe the behaviour of foreach in this case is extremely dangerous. Imagine there's such a "referential" loop in a library which is included by some other scripts and it operates on $_SESSION or some other important array. Now any foreach in the other scripts might corrupt the base array without even knowing it - just because the name of the "value" variable is the same. The main purpose of foreach itself is (as the name implies) to do something "for each" element of the array, i.e. to provide an easier way to traverse an array than the other loop constructs and not to touch other variables or references that are still pointing somewhere, unless it's done implicitly in the body of the loop. IMHO, foreach has to clear the variables used for key-value pairs, i.e. to unset the reference before proceeding, just like it resets the array pointer. Otherwise we have to put a big red note in the manual to always unset variables after using the referential syntax or to use $long_descriptive_names_for_references, which is a little unreasonable in the current context. ------------------------------------------------------------------------ [2004-10-08 08:54:14] der...@php.net Thank you for taking the time to write to us, but this is not a bug. Please double-check the documentation available at http://www.php.net/manual/ and the instructions on how to report a bug at http://bugs.php.net/how-to-report.php Right, thanks for that analysis. No bug here. (And no, we can't unset it by default, as people might use this for some weird reason). ------------------------------------------------------------------------ [2004-10-07 21:12:18] gardan at gmx dot de There is no corruption. It is easy to explain this behaviour. Take the following: $arr = array(1 => array(1, 2), 2 => array(1, 2), 3 => array(1, 2)); foreach($arr as &$value) { } foreach(array(1,2,3,4,5) as $key => $value) { } echo $test[3]; After the first foreach() loop what you have in $value is a reference to the last element in $arr (here: to array(1,2)). Now, when the next foreach loop assigns a value to $value, it assigns this value to where $value points, that is: to the last position of $arr. Now in the second foreach() the last item of $arr becomes first 1, then 2, etc and in the end, what you get as output by this program, is: 5 Not the expected: Array Now this is indeed very confusing if you don't know what's going on. Solution: unset $v before a new foreach() loop (maybe this could be done by php by default?) ------------------------------------------------------------------------ [2004-10-04 16:37:43] yaroukh at email dot cz I believe the problem I'm gonna describe here has the same roots as the one fletch has described ... Try this: <? class ArrayWrapper { protected $slots; public function __construct($slots = Array()) { $this->slots =& $slots; } public function & getSlot($idx) { if(IsSet($this->slots[$idx])) return $this->slots[$idx]; return null; } public function getLength() { return Count($this->slots); } } // fill the array and create object { $slots = Array( 'zero', 'one', 'two', 'three', 'four' ); $aw = new ArrayWrapper($slots); // } // output A var_dump($aw); // iteration 1 for($idx = 0; $idx < $aw->getLength(); $idx++) $aw->getSlot($idx); // output B; everything is OK var_dump($aw); // iteration 2 for($idx = 0; $idx < $aw->getLength(); $idx++) if($aw->getSlot($idx)) { } // output C; elements have been changed to references var_dump($aw); ?> As you can see in output "C" the second iteration altered elements of the array - the elements have been turned into references. (Or did I get the ampersand-sign wrong?) The problem is that I loose control over the REAL objects and the elements get changed unreasonably later in the script. I kind of understand what's the diference between getSlot() and getSlot() used within an if-loop (I guess the scope created by if-loop makes the difference), but I'm not sure this difference entitles PHP to alter my array. Can anyone explain this to me? Is it a bug or a regular behaviour? ------------------------------------------------------------------------ [2004-09-06 05:54:50] fletch at pobox dot com Description: ------------ foreach with a reference seems to corrupt the last element in an array Reproduce code: --------------- <?php $array = array(1,2,3); foreach( $array as &$item ) { } print_r( $array ); foreach( $array as $item ) { } print_r( $array ); ?> Expected result: ---------------- Array ( [0] => 1 [1] => 2 [2] => 3 ) Array ( [0] => 1 [1] => 2 [2] => 3 ) Actual result: -------------- Array ( [0] => 1 [1] => 2 [2] => 3 ) Array ( [0] => 1 [1] => 2 [2] => 2 ) ------------------------------------------------------------------------ -- Edit this bug report at http://bugs.php.net/bug.php?id=29992&edit=1