6

I am writing PHP code to make some transformations of every value in an array, then to add some values to the array from external source (MySQL cursor or, say, another array). If I use foreach and a reference to transform array values

<?php
$data = array('a','b','c');

foreach( $data as &$x ) 
    $x = strtoupper($x);

$extradata = array('d','e','f'); 
// actually it was MySQL cursor

while( list($i,$x) = each($extradata) ) {
    $data[] = strtoupper($x);
}

print_r($data);
?>

(Here it is in PHPfiddle)

than data is beeing corrupted. So I get

Array ( [0]=>A [1]=>B [2]=> [3]=>D [4]=>E [5] =>F )

instead of

Array ( [0]=>A [1]=>B [2]=>C [3]=>D [4]=>E [5] =>F )

When I use no reference and write

foreach( $data as &$x ) 
    $x = strtoupper($x);

transformation does not occur, of course, but data is not corrupted too, so I get

Array ( [0]=>a [1]=>b [2]=>c [3]=>D [4]=>E [5] =>F )

If I write code like this

<?php
$result = array();

$data1 = array('a','b','c');

foreach( $data1 as $x ) 
    $result[] = strtoupper($x);

$data2 = array('d','e','f'); 
// actually it was MySQL cursor

while( list($i,$x) = each($data2) ) {
    $result[] = strtoupper($x);
}

print_r($result);
?>

everything works as expected.

Array ( [0]=>A [1]=>B [2]=>C [3]=>D [4]=>E [5] =>F )

Of course, I copying data solves the problem. But I would like to understand what is the strange trouble with that reference and how such troubles can be avoided. Maybe it is generally bad to use PHP references in code (like many people say about C-pointers)?

  • I've confirmed the same behaviour on my PHP installation (win64-5.4.7) - I have no idea why only `C` disappears though. If you use `var_dump` instead you will see that `C` has come `&NULL`. – Halcyon Oct 25 '13 at 15:29
  • 1
    A strange code. What are you trying to achieve? Why don't you merge the arrays first? (`array_merge()`, http://www.php.net/array_merge) And afterwards run `array_map()` (http://www.php.net/array_map) to change the case. – feeela Oct 25 '13 at 15:29
  • > Why don't you merge the arrays first? Originally I was trying append values fetched from MySQL cursor. Later I simplified the code to put it into PHPfiddle, making array of cursor . – user2920383 Oct 25 '13 at 15:43
  • @deceze "possible duplicate" - I have tried and saw that the pair of subsequent `foreach` loops works without troubles! But `While` after `foreach` causes this strange behaviour. – user2920383 Oct 25 '13 at 15:49
  • The step-by-step explanation should explain what's happening. You simply need to think about what happens when you *assign to a reference*. Whether that happens by a second `foreach` or a `while` or a simple `=` is irrelevant. – deceze Oct 25 '13 at 15:57

3 Answers3

15

References mechanism of PHP language has specific feature, that is not common to other programming languages. It is commonly accepted that object reflects all changes, made to its properties through any reference to it. But assignment to reference itself is either prohibited or makes the reference point to another object. Instead of this, assignment to reference in PHP substitutes the whole underlying object (object pointed by reference) with the one, beeing assigned. So

$a = 1; $b = 2;
$r = &$a;
$r = $b;
echo $a; // will output '2'

This is true to assigment, but not true to unset call, which will not destroy underlying object, but break the link between the reference and pointed object.

$a = 1; $b = 2;
$r = &$a;
unset($r); //!
$r = $b;
echo $a; // will output '1'

This reference behaviour is useful in some cases, but it is often misunderstood, what leads to problems like shown in question.

To aviod problems with PHP references you should:

  • Unset every reference as early as possible (at the point when it becomes unnecessary).

So, this code will work

<?php
$data = array('a','b','c');

foreach( $data as &$x ) 
    $x = strtoupper($x);
unset($x);

$extradata = array('d','e','f'); 
// actually it was MySQL cursor

while( list($i,$x) = each($extradata) ) {
    $data[] = strtoupper($x);
}

print_r($data);
?>
  • Generally it is considered a bad style to reuse local variable names in several control structures.

So the following code will work too

<?php
$data = array('a','b','c');

foreach( $data as &$x ) 
    $x = strtoupper($x);

$extradata = array('d','e','f'); 
// actually it was MySQL cursor

while( list($i,$y) = each($extradata) ) {
    $data[] = strtoupper($y);
}
mas.morozov
  • 2,666
  • 1
  • 22
  • 22
  • 1
    Thanks! A *strange* language feature! Where it could be useful? – user2920383 Oct 25 '13 at 15:50
  • 1
    It is used by passing variable reference to function, where it gets assigned and the variable value changes accordingly. If there was no such reference assigment rule, you would have to wrap your value into object to be able to pass it by reference (like it happens in JS). – mas.morozov Oct 25 '13 at 16:03
  • And why 'C' is beeing replaced with null? – user2920383 Oct 25 '13 at 16:20
  • It is consequently replaced with every value from `each`: 'd', 'e', 'f' and finally null (after each while-loop breaks). – mas.morozov Oct 25 '13 at 16:23
  • `PHP references you should` not use them at all if possible ;) – Izkata Oct 25 '13 at 16:24
0

This works for me.... Maybe I could have done it differently if I knew what you were trying to do .. yet this should work.

$data = array('a','b','c');

foreach( $data as &$x ) 
    $x = strtoupper($x);

$extradata = array('d','e','f'); 
// actually it was MySQL cursor

foreach ($extradata as &$x) {
    $data[] = strtoupper ($x);
}
deceze
  • 510,633
  • 85
  • 743
  • 889
xlordt
  • 521
  • 4
  • 15
  • 1
    It's not so much about what OP is trying to accomplish, more that OP sees strange behavior for which OP is asking for an explanation. – Halcyon Oct 25 '13 at 15:42
  • @deceze, answering your edit... nah, I am just bad at grammar for personal reasons.. but I am getting better :) – xlordt Oct 25 '13 at 15:51
0

You're using $x again in the loop over $extradata, which causes the references to go wonky.

This works:

$data = array('a','b','c');

foreach( $data as &$x )
    $x = strtoupper($x);

$extradata = array('d','e','f');
// actually it was MySQL cursor

while( list($i,$anything_but_x) = each($extradata) ) {
    $data[] = strtoupper($anything_but_x);
}

print_r($data);

  • Don't reuse variables
  • Avoid references
Halcyon
  • 57,230
  • 10
  • 89
  • 128