php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #29992 foreach by reference corrupts the array
Submitted: 2004-09-06 05:54 UTC Modified: 2018-04-30 03:42 UTC
Votes:9
Avg. Score:4.7 ± 0.5
Reproduced:8 of 8 (100.0%)
Same Version:5 (62.5%)
Same OS:4 (50.0%)
From: fletch at pobox dot com Assigned:
Status: Not a bug Package: Scripting Engine problem
PHP Version: 5.0.1 OS: linux
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If this is not your bug, you can add a comment by following this link.
If this is your bug, but you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: fletch at pobox dot com
New email:
PHP Version: OS:

Further comment on this bug is unnecessary.

 

 [2004-09-06 05:54 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
)

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2004-10-04 16:37 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?
 [2004-10-07 21:12 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?)
 [2004-10-08 08:54 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).
 [2006-08-07 14:18 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.
 [2010-05-24 19:20 UTC] looris at gmail dot com
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.
 [2010-11-27 10:28 UTC] tarama at hotmail dot com
I highly agree with looris. I had to patch each use of hundreds of foreach loops in my code due to this weird behaviour. The unset() tips at the end of each loop does the job, but from my point of view it's really not logical. It caused really hard to find bugs in my code and I prefer to not imagine how many PHP scritps are running on the web with the same "bug" silently breaking things.
 [2010-11-27 14:59 UTC] johannes@php.net
I explained this with some pictures in my blog: http://schlueters.de/blog/archives/141-References-and-foreach.html

And no, unsetting after the loop is no option as it is inconsistent with other loops and there are valid reasons for keeping it after the loop. Something along the lines of

foreach ($array as &$item) {
    if ($item == "foo") {
        break;
    }
}

$item = "bar";
 [2011-07-01 06:41 UTC] daniel at milde dot cz
I agree with Looris. This behaviour is dangerous and no one expects it.

I suggest unsetting the variable BEFORE each loop. This should cause no harm.
 [2011-07-13 06:37 UTC] martijn at twotribes dot com
Can someone please promote this "bogus" status to an actual bug? It completely baffles me why something so obviously wrong, has been present in PHP for almost 7(!) years. Come on guys, fix this!
 [2011-07-13 06:48 UTC] martijn at twotribes dot com
To elaborate of why I strongly feel this is a bug and not a 'feature':

$a = array('a', 'b', 'c', 'd');
foreach ($a as &$v) { }
var_dump($a);

One would expect that every element of $a is a string. Well it was, up until I did that foreach-with-reference. That changed the last element into string reference:

array(4) {
  [0]=>
  string(1) "a"
  [1]=>
  string(1) "b"
  [2]=>
  string(1) "c"
  [3]=>
  &string(1) "d"
}

The PHP guys can claim that this is correct behavior all they want, but it is fundamentally wrong from a design perspective that an array changes, without doing anything to its elements.
 [2011-07-15 11:57 UTC] johannes@php.net
daniel,

unsetting it before might also cause a wrong result. Simple example:
<?php
function search_element($array, &$var) {
    foreach ($array as &$var) {
        if ($condition) {
            return true;
        }
    }
}
$array=array(1,2,3,4,5,6);
$var = null;
search_element($array, $var);
?>

This is simplified and probably no good architecture but such designs might make sense in some situations and breaking that adds a larger inconsistency than the current "surprising but consistent" behavior.


martijn,

Of course there is a reference at the end, absolutely expected and consistent with the language.
 [2011-07-18 05:03 UTC] martijn at twotribes dot com
Well, it is expected by the people who designed the language perhaps, but not by me. Iterating through an array, without doing anything, shouldn't change the array. Period. If I do something similar in another language like C++, this will never be the result.
 [2011-09-02 15:13 UTC] publcishady at gmail dot com
If you describe how it works that's not an excuse for unexpected results. I understand why the last element becomes a reference, but I don't understand why it SHOULD become a reference. That's obviously a bug for me.
 [2011-10-20 04:56 UTC] bruce at kaskubar dot com
With all due respect to those who spend their time developing, debugging, and explaining PHP, BALDERDASH!

Elsewhere, those of us who continue to claim "bug" are supposed to be chastened by all the explanations that have been provided over the years. The fact that the reports persist and the explanations grow is evidence contrary to the finding of Bogus and in support of what is expected behavior.

Fletch's example, for example, is real and reproducible through at least v5.3.2.

How in the world can it be expected for a (second) loop and its object to perform as though no related instructions were executed previously, and then for that interaction to raise its ugly head for only the last iteration? It cannot be. It can be explained. But so can an earthquake be. That doesn't make it expected.

I am unaware of any other case where prior use of a variable affects subsequent use of the same-named variable where its value is being explicitly reset or, as in the case of foreach, implicitly reset by virtue of its purpose. (That is, we can use $i as a loop counter here and as a file handle there and as long as we don't cross their purposes in the space-time continuum, all is well.)

The only bogus thing about this bug report and all its cousins is their shared status.
 [2012-02-09 12:33 UTC] robbie at shapehq dot co dot uk
I feel very strongly that this behavior is wrong. It's not consistent with other languages and will cause great confusion!
 [2012-02-09 16:49 UTC] rasmus@php.net
No matter how you paint this, it isn't a bug. What you are really asking for is 
some sort of block-level scoping. As such, it should be proposed as a feature 
request, but it would be a major change in the language. The simplest way to 
illustrate that is to just unroll the loops. Take this example:

$a = array(1,2);
foreach($a as &$b) { }
foreach($a as $b) { }
print_r($a);

The problem here is that people find it inconsistent that the 2nd loop changes 
$a. But if we unroll the loops and write the exactly equivalent code without the 
foreach construct we get:

$a = array(1,2);

// First loop, $b is a reference to each element in $a
$b = &$a[0];
$b = &$a[1];

// Second loop, $b is assigned the value of each element in $a
$b = $a[0];
$b = $a[1];

Those two pieces of code are identical in every way. The thing that confuses 
people is that $b is still a reference $a[1] going into the second loop.

Since PHP doesn't have block-level scoping, there is nothing in the language 
that would permit $b to be unset between the two loops without introducing a 
major inconsistency. In fact there is plenty of code that relies on this fact 
which would break if we made such an arbitrary change.

I suppose what you are asking for is syntax along the lines of:

$a = array(1,2);
{
  local $b = &$a[0];
  $b = &$a[1];
}
{
  local $b = $a[0];
  $b = $a[1];
}

Where $b is locally scoped in each of those blocks and it might look like this 
in a foreach case:

$a = array(1,2);

foreach($a as local &$b) { }
foreach($a as local $b) { }

Without such a scoping syntax change, something as simple as:

forach(array(1,2,3) as $b) { }
echo $b;

where the code fully expects $b to be 3 would break.
 [2012-02-09 16:55 UTC] looris at gmail dot com
No one cares about the technical explanation about why this happens at a low 
level. I'm quite puzzled you are still in denial about this bug.
 [2012-02-09 17:20 UTC] rasmus@php.net
What do you mean you con't care about the explanation?

Ok, simple question then. Do you expect this to output 3?

foreach(array(1,2,3) as $b) { }
echo $b;

If you do, then you don't want us to fix this "bug" because fixing it would mean 
$b is not 3 here.
 [2012-03-01 18:31 UTC] paul dot dillinger at gmail dot com
Rasmus, I think they might be having the same problem than I am where the array 
literally changes as soon as I enter the foreach.  I've given an in depth 
explanation at: http://codeigniter.com/forums/viewthread/201487/ , but I'll give 
a summary here. I'm using a newer version of PHP (5.3.8) and foreach is 
corrupting my array even when it's not being passed by reference.

My original code read something like this:
if(!empty($packages)){
            /* $this->data['external_js'] is normal */
            foreach($packages as $item){
                /* $this->data['external_js'] has changed */

I noticed that one of my javascript files that this function is packing in to a 
single package as not present.  Even more odd was another was in the package 
twice.  So I started logging the $this->data['external_js'] array to FirePHP to 
see where the error was happening.  Strangely enough it happened immediately 
after a foreach.  I decided to make a separate copy of the array as a "just in 
case" and report that.  It changed the exact same way.  I need to literally hand 
build my JS packages as I can't figure out any way to stop this array from 
changing once it enters the foreach.

Here is the troubleshooting code with comments:

if(!empty($packages)){ // checking to see if there are multiple files to be 
packaged together
            if($type=='js'){ // check to see if it's javascript as that was the 
package that had the problem
                $ext_js_for_firephp = $this->data['external_js']; // found that 
$this->data['external_js'] was changing so I assign it to a new variable 
exclusively for logging to FirePHP, this variable exists NO WHERE ELSE in the 
code.
                fb_log('$ext_js_for_firephp before', $ext_js_for_firephp); // 
Log to FirePHP

/* fb_log function for reference
function fb_log($Label,$Object=null){
    $firephp = FirePHP::getInstance(true);
    if(empty($Object)){
        $Object = $Label;
        $Label = NULL;
    }
    $firephp->log($Object, $Label);
}
*/
            }

            foreach($packages as $item){ // Starting the foreach
                if($type=='js'){ // Again problem was with JS package changing
                    fb_log('$ext_js_for_firephp after', $ext_js_for_firephp); // 
Log to FirePHP, but now the value is different.
                }

// AGAIN this happened before I started logging the vars, so logging is not 
causing the issue.  It's not an error with the logging output, as this is 
exactly what the file being built had in it.

/* RESULT */

/* Before FirePHP returns:
$ext_js_for_firephp before = array(
    [0] => array(
        ['template_id'] => 30
        ['js_id'] => 9
        ['id'] => 9
        ['library_name'] => 'modernizr'
        ['file_name'] => 'modernizr.min.js'
        ['version_major'] => 2
        ['version_minor'] => 0
        ['version_build'] => 6
        ['static'] => 1
        ['package'] => 0
        ['footer'] => 0
        ['priority'] => 100
    )
    [1] => array(
        ['template_id'] => 30
        ['js_id'] => 12
        ['id'] => 12
        ['library_name'] => 'default'
        ['file_name'] => 'default.js'
        ['version_major'] => 0
        ['version_minor'] => 0
        ['version_build'] => 4
        ['static'] => 1
        ['package'] => 1
        ['footer'] => 0
        ['priority'] => 90
    )
    [2] => array(
        ['template_id'] => 37
        ['js_id'] => 11
        ['id'] => 11
        ['library_name'] => 'jquery-ui-custom'
        ['file_name'] => 'jquery-ui-1.8.11.custom.min.js'
        ['version_major'] => 1
        ['version_minor'] => 8
        ['version_build'] => 11
        ['static'] => 1
        ['package'] => 0
        ['footer'] => 0
        ['priority'] => 0
    )
)
*/

/* After FirePHP returns:
$ext_js_for_firephp after = array(
    [0] => array(
        ['template_id'] => 30
        ['js_id'] => 9
        ['id'] => 9
        ['library_name'] => 'modernizr'
        ['file_name'] => 'modernizr.min.js'
        ['version_major'] => 2
        ['version_minor'] => 0
        ['version_build'] => 6
        ['static'] => 1
        ['package'] => 0
        ['footer'] => 0
        ['priority'] => 100
    )
    [1] => array(
        ['template_id'] => 30
        ['js_id'] => 12
        ['id'] => 12
        ['library_name'] => 'default'
        ['file_name'] => 'default.js'
        ['version_major'] => 0
        ['version_minor'] => 0
        ['version_build'] => 4
        ['static'] => 1
        ['package'] => 1
        ['footer'] => 0
        ['priority'] => 90
    )
    [2] => array(
        ['template_id'] => 30
        ['js_id'] => 12
        ['id'] => 12
        ['library_name'] => 'default'
        ['file_name'] => 'default.js'
        ['version_major'] => 0
        ['version_minor'] => 0
        ['version_build'] => 4
        ['static'] => 1
        ['package'] => 1
        ['footer'] => 0
        ['priority'] => 90
    )
)
*/
 [2012-03-01 18:52 UTC] rasmus@php.net
Paul, my guess is that $item is a reference to an element in the $packages array 
going into this loop. Try using a different variable there.
 [2012-03-19 18:51 UTC] paul dot dillinger at gmail dot com
Rasmus,

Thanks for looking at this.  I found the problem.  I would still call it a bug, 
but I will describe it and you can decide.  You are the man after all.  You were 
right, I was passing a variable by reference in the few lines of code in front 
of my example above: 


        foreach($this->data['external_'.$type] as &$item){
            if(!empty($item['package'])){
                $packages[] = $item;
                $library_names[] = $item['library_name'];
                unset($item);
            }
        }

/* Code in example above goes here */

BUT, where I see this as a bug was: $packages (the array that was getting 
changed) was a new array created from the data of each $item.  $packages was 
never being referenced, though the variable $item it was created from was.  So, 
it should be a copy of the data and not THE data right? 

I fixed it by simply not trying to pass by reference and changing unset($item) 
to unset($this->data['external_'.$type]).  Looking at it, that was the way to do 
it from the beginning.  I see that, but I'm not sure why $packages gets changed 
down the road (it was correct immediately after this, but it and all copies of 
it change inside the next foreach).  Any thoughts?
 [2012-05-04 08:04 UTC] email at stevemann dot net
rasmus@php.net asked
"Ok, simple question then. Do you expect this to output 3?"

foreach(array(1,2,3) as $b) { }
echo $b;

I would much prefer it not to output 3. Personally I think it would make a lot more sense and be a lot safer to have the array element references scoped to the foreach block - so effectively being unset after the block has run. Having the last element of the array floating around outside of the block is very dangerous in my view and can lead to silent errors. As someone else mentioned, I hate to think how much incorrect data there is out there because of the last array element being accidentally changed outside of the block.

derick@php.net rather flippantly said:
"no, we can't unset it by default, as people might use this for some weird reason."

I can think of plenty of non-weird reasons why people might want this behaviour. But if it was unset by default, it's a simple matter to assign the reference to a variable defined outside of the block thereby making it available outside the foreach. In other words, like this:

$c = NULL;
foreach(array(1,2,3) as $b) {
	$c = $b;
}
unset($b);// simulates block-scoping of $b
echo $c;

This is not a bug, but I believe it's dangerous behaviour of PHP as it would seem quite logical to assume that the element references are scoped to the foreach block only - witness the many comments in this thread to that effect. So my vote would be to change this behaviour to block-scoping in a future version.
 [2012-06-29 18:52 UTC] iam4webwork at hotmail dot com
I appreciate the explanation that Rasmus provides -- thank you!
One small but troublesome detail:

The first foreach changes the array by making $a[1] a reference
variable while $a[0] remains a normal variable.

$a = array(1,2);
foreach($a as &$e){}
var_dump($a,$e); // $a[1] == &int 2    $e == 2
foreach($a as $e){} $a[1] == &int 1    $e == 1
var_dump($a,$e); // $a[1] now points to last value of $a which is $a[0]

How about adding a switch so that users who don't want or understand
this behavior can turn it off?  Then it would be up in front of the 
documentation and would be less liable to be overlooked by users who 
fail to scroll down to the colored box.

Even if PHP were to have lexical scope (how hard would that be to 
implement and why can't PHP evolve that way?), that doesn't change 
the fact that the first loop doing seemingly nothing, does change the array.
 [2012-10-24 22:11 UTC] newms87 at gmail dot com
I understand this functionality, and I do agree that it is not a bug. It seems 
at the core of PHP that this is what would happen, but it does seem very 
unintuitive to me having used a variety of other languages. The result is not 
expected and has  caused several very hard to find bugs for me. 

Would it be possible to have PHP generate a E_NOTICE when using the same $var in 
both a foreach and afterwards when in a higher scope? EG: 

foreach($args as &$a){} 

$a = 'hello';  // this would generate an E_NOTICE

Then maybe have the option to turn off (or on by default) the E_NOTICE warnings 
in the ini settings?
 [2012-10-25 07:37 UTC] email at stevemann dot net
I don't think this is going to go anywhere - seems to have reached a stalemate. So I have just retrained my head to automatically create foreach loops thus:

foreach($array as $item){

}unset($item);

If you need access to the last $item outside the loop, then just do it somewhere before the unset($item).

Seems to me this thread is being accessed periodically by developers scratching their heads after discovering similar oddities happening with their foreach loops. My advice would be to do something similar to the above and just live with it.
 [2012-10-25 15:49 UTC] paul dot dillinger at gmail dot com
I still say this is a bug, so here's all the code you need to re-produce it:

<pre>
<?php
$packages = array();
$library_names = array();
$ext_js =
    array(
        array(
            'name' => 'myname1',
            'attrib' => 'something1',
            'package' => true,
            'library_name' => 'package1'
            ),
        array(
            'name' => 'myname2',
            'attrib' => 'something2',
            'package' => true,
            'library_name' => 'package1'
            ),
        array(
            'name' => 'myname3',
            'attrib' => 'something3',
            'package' => false
            ),
        array(
            'name' => 'myname4',
            'attrib' => 'something4',
            'package' => false
            )
        );

foreach($ext_js as &$item){
	if(!empty($item['package'])){
                $packages[] = $item; // Not using &, so should be a copy, not a 
reference corrent?
                $library_names[] = $item['library_name'];
                unset($item);
	}
}
if(!empty($packages)){
/*A*/       print_r($ext_js);
            foreach($packages as $item){
/*B*/           print_r($ext_js);
            }
}
?>
</pre>

Look at the output on the last item.  Instead of unset removing the item from 
the array $ext_js (which is what I thought it would do), it corrupts the array.  
The array is fine though unless you go in to another foreach using another 
$item.

Changing the variable name on the second foreach to something OTHER than $item 
(I used $itemm) fixes it.

Bug.
 [2012-10-25 17:01 UTC] paul dot dillinger at gmail dot com
I understand that my explanation above isn't 100% accurate (unset really doesn't 
have anything to do with it), but that doesn't change that the expected behavior 
is not working.

Rasmus said:
	"Ok, simple question then. Do you expect this to output 3?

	foreach(array(1,2,3) as $b) { }
	echo $b;

	If you do, then you don't want us to fix this "bug" 
	because fixing it would mean $b is not 3 here."

I say in the example above, would you expect a print_r of the same array to 
return 1,2,2?  My issue was that the content of the entire array.

Honestly I've been programming since I was eight years old.  That was 1985.  If 
this was confusing the hell out of me then something's wrong.

Here's an even simpler example:

<pre>
<?php
$clean = array(1,2,3,4);
foreach($clean as &$item){
  // Nothing
}
echo "A:\n";
/*A*/       print_r($clean);
echo "B:\n";
            foreach($clean as $item){
/*B*/           print_r($clean);
            }

$clean = array(1,2,3,4);
foreach($clean as &$item){
  // Nothing
}
echo "C:\n";
/*C*/       print_r($clean);
echo "D:\n";
            foreach($clean as $not_item){
/*C*/           print_r($clean);
            }
?>
</pre>

A and B output the same array differently with no modification (not expected).
C and D are the same (expected).  The only change was not re-using the name 
$item.

How can we make it so that using &$item in one foreach and then using the same 
variable name ($item) in a different foreach does not change the original array?
 [2013-05-20 09:51 UTC] richard at ejem dot cz
This IS a bug and SHOULD be finally fixed. It is weird, hard to debug and unexpected behavior which took me many hours of life finding the problem. Almost every modern programming language has a variable context, which guarrantees the foreach variable is not visible after exiting the loop.

Anyone is using it intentionally for "weird reason"? come on guys, almost every bug can be used for some hacking or weird reasons, will it stop you from fixing other bugs? no!

Sorry for "mee-to" like post, but I do it for the good of next PHP programmer generations who will also lose hours of their lives discovering this nonsense. Please fix it.
 [2013-05-20 15:57 UTC] paul dot dillinger at gmail dot com
OK, I went over this some more.

<pre>
<?php
// Fresh array
$clean = array(1,2,3,4);

foreach($clean as &$item){
  // Nothing is modified in the array, but $item now exists
}

/*##############################################################################
 * $item persists outside of foreach and is now $clean[3] 
 * See the warning on http://php.net/manual/en/control-structures.foreach.php
 * print_r($item); // would return 4 you you uncommented this.
 * unset($item); // This would remove the pointer to $clean[3].  Expected.
 *############################################################################*/

echo "A:\n";
/*A*/       print_r($clean); // $clean is still unmodified

echo "B:\n";
            foreach($clean as $item){  
/*##############################################################################
 * Using AS $item SETS $item TO the current $item value (a.k.a. $clean[0], etc.)
 * Essentially foreach($clean as $item) is short hand for something like:
 * $x=0;while($x < count($clean)){$item=$clean[$x]; ### your code ###  $x++;}
 * The problem I had was that I did not expect foreach to be able to set on call
 *############################################################################*/

/*B*/           print_r($clean);
            }
?>
</pre>

So creating the variable is documented, and it isn't a bug.  
The ability to set the value could be made clearer though.
 [2013-05-21 06:38 UTC] email at stevemann dot net
Agreed this is not a bug, it's expected behaviour. But it's dangerous as it can slip by without being noticed. It almost certainly means there are 
thousands of sites which are exhibiting wrong behaviour because of this and no-one realises. Surely the concept of scoping the 'as' variable to the 
foreach enclosure only can't be considered bad form. It would make so much more sense to 'opt-in' to retrieving the variable outside of the enclosure 
(by assigning to another persistent variable within the enclosure) rather than the current 'opt-out' system (using unset()) which, unless you happen 
to have read the warning is HIGHLY DANGEROUS.
 [2013-12-04 21:50 UTC] gray dot bowman at gmail dot com
Just wanted to chime in that 9 years on, this is still totally unexpected behavior.  Today, two professional developers with years experience spent a couple hours trying to figure out why an array that was demonstrably intact had its last element corrupted for apparently no reason once entering a foreach.
 [2014-02-11 00:27 UTC] xjis at msn dot com
guys... i read through this entire thread and now i totally get it.

this is just a weird "side-effect" of having a language being "function-level scope" instead of "block-level scope" and with referencing support.

and as long as the language stays being 'function-level scope' that also has the referencing support, this is the expected behavior; and there's no easy way around it. 

what ppl are expecting is exactly that of a block-level scope languages: having &v not referencing anything after for-loop block ended. that's why ppl are keep referring to C/C++/Java because these are all block-level scope languages; and why everyone wants for-loop to "auto-unset" the &v after for-loop block ended; that would basically emulate/mimic the behavior of block-level scope languages; but that is simply not the expected behavior for function-level scope languages so you can't just make a special case only for for-loop blocks 
--why would you make a special exception just for for-loop; why not for the entire language? then now, you are asking PHP language to completely change itself from being a function-level scope language into a block-level scope language

btw, javascript is also a function-level scope language, but it doesn't have this particular problem because it doesn't support referencing
 [2014-02-11 03:34 UTC] xjis at msn dot com
well, i guess to be more exact, i should've said php doesn't have block scope support whereas java/c++ does. 

now i see that even javascript has added block scoping starting 1.7 (need to use 'let' instead of 'var' to declare block-scoped variables)
 [2014-06-20 22:09 UTC] steve at fancyguy dot com
I have done this intentionally in some very lazy prototypes.

    foreach($data as $k => &v) {
        if (meets_complex_precondition($k)) {
            break;
        }
    }
    // do some stuff with $v potentially modifying it later
    unset($v);

Comes in handy on occasion when working recursively by reference in deep arrays. Neat trick like 'array_map(null, $arr1, $arr2)'.
 [2014-12-06 23:33 UTC] dave dot mittner at gmail dot com
Though this may not be a "bug" it's certainly dangerous and unexpected enough to deserve the level of importance.

As has been said, people expect the behavior of block-level scoping. I don't come from Java or C++. I've programmed PHP for over a decade. I don't expect this kind of scope treatment because of other languages. I expect it because it's a clean and logical practice that's far safer than the contrary. Best practice for *any* language is to assume limited scope in blocks. I've known that since I was a teenager.

In short, this should be changed. Even if it's a hack like unsetting it, that's safer than the current processing model. And I don't buy that "we can't change it because people use it" crap. I've had to rewrite entire systems due to changes in how PHP works. Stuff breaking is a risk any time you update PHP and why you should pay attention to change lists.
 [2015-01-01 20:18 UTC] chealer at gmail dot com
The problematic behavior behing this issue is being tracked in #62132.
 [2016-05-20 14:34 UTC] ourtusenka at yandex dot ru
I have the same problem with php 5.6 on OpenServer.
Before I never meet it; I don't use reference. But foreach do the same with my array.
So:
foreach ( $items as $item ) {
    //..echo single $item
}
Last element of $items is disappeared;
 [2017-06-09 15:45 UTC] qdinar at gmail dot com
issue with var_dump shown at "[2011-07-13 06:48 UTC] martijn at twotribes dot com" is an independent thing; array element looks like it is somehow changed even if it is just only referenced, or it is shown by var_dump, ie might be var_dump shows an array element which is referenced from outside; i have googled about that issue and have found a comment at http://php.net/manual/en/language.references.php , "Dave at SymmetricDesigns dot com 9 years ago", (most voted) which shows that it is not only what is shown by var_dump, but the array element changes its behaviour. that issue is conceptually independent from explanations, which do not show the "&" in var_dump output, given here for this issue, because original array element needs not to become a reference itself to reproduce this issue, it is rewritten because it is referenced by $item.
 [2018-04-30 03:33 UTC] dan dot chase at chasetechnologysolutions dot com
Sounds like another example things in PHP do not mirror other languages. If PHP doesn't have scoping, and we're all finding out about it years into our coding when we finally try to use foreach on a reference, not too bad.. however, I just corrected a report from LAST YEAR where we didn't know it was repeating a score in a sporting event because of this, I hope someone isn't too pissed they didn't get a trophy.. anyway, the lesson here kiddies, use a language you can trust!
 [2018-04-30 03:42 UTC] requinix@php.net
-Block user comment: No +Block user comment: Yes
 [2018-04-30 03:42 UTC] requinix@php.net
If I don't know that I need to change the oil in my car and the engine seizes, is it the car's fault?

More than 13 years of comments here. Now's a good time to stop.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Apr 18 14:01:31 2024 UTC