PHP Bugs  
php.net | support | documentation | report a bug | advanced search | search howto | statistics | login

go to bug id or search bugs for  

Bug #29992 foreach by reference corrupts the array
Submitted:6 Sep 2004 5:54am UTC Modified: 7 Aug 2006 2:18pm UTC
From:fletch at pobox dot com Assigned to:
Status:Bogus Category:Scripting Engine problem
Version:5.0.1 OS:linux
Votes:9 Avg. Score:4.7 ± 0.5 Reproduced:8 of 8 (100.0%)
Same Version:5 (62.5%) Same OS:4 (50.0%)
View/Vote Developer Edit Submission

[6 Sep 2004 5:54am UTC] 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
)
[4 Oct 2004 4:37pm UTC] 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?
[7 Oct 2004 9:12pm UTC] 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?)
[8 Oct 2004 8:54am UTC] derick@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).
[7 Aug 2006 2:18pm UTC] kouber@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.

RSS feed | show source 

PHP Copyright © 2001-2009 The PHP Group
All rights reserved.
Last updated: Sat Nov 21 10:30:49 2009 UTC