Edit report at https://bugs.php.net/bug.php?id=29992&edit=1

 ID:                 29992
 Comment by:         newms87 at gmail dot com
 Reported by:        fletch at pobox dot com
 Summary:            foreach by reference corrupts the array
 Status:             Not a bug
 Type:               Bug
 Package:            Scripting Engine problem
 Operating System:   linux
 PHP Version:        5.0.1
 Block user comment: N
 Private report:     N

 New Comment:

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?


Previous Comments:
------------------------------------------------------------------------
[2012-06-29 18:52:20] 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-05-04 08:04:56] email at stevemann dot net

ras...@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.

der...@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-03-19 18:51:24] 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-03-01 18:52:14] ras...@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-01 18:31:08] 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
    )
)
*/

------------------------------------------------------------------------


The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at

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


-- 
Edit this bug report at https://bugs.php.net/bug.php?id=29992&edit=1

Reply via email to