Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP pointer and variable conflict

Tags:

php

pointers

I have a question about PHP and the use of pointers and variables.

The following code produces something I wouldn't have expected:

<?php
$numbers = array('zero', 'one', 'two', 'three');

foreach($numbers as &$number)
{
  $number = strtoupper($number);
}

print_r($numbers);

$texts = array();
foreach($numbers as $number)
{
  $texts[] = $number;
}

print_r($texts);
?>

The output is the following

Array
(
    [0] => ZERO
    [1] => ONE
    [2] => TWO
    [3] => THREE
)
Array
(
    [0] => ZERO
    [1] => ONE
    [2] => TWO
    [3] => TWO
)

Notice the 'TWO' appearing twice in the second array.

It seems that there is a conflict between the two foreach loops, each declaring a $number variable (once by reference and the second by value).

But why ? And why does it affect only the last element in the second foreach ?

like image 860
nIcO Avatar asked Oct 26 '11 09:10

nIcO


3 Answers

The key point is that PHP does not have pointers. It has references, which is a similar but different concept, and there are some subtle differences.

If you use var_dump() instead of print_r(), it's easier to spot:

$collection = array(
    'First',
    'Second',
    'Third',
);

foreach($collection as &$item){
    echo $item . PHP_EOL;
}

var_dump($collection);

foreach($collection as $item){
    var_dump($collection);
    echo $item . PHP_EOL;
}

... prints:

First
Second
Third
array(3) {
  [0]=>
  string(5) "First"
  [1]=>
  string(6) "Second"
  [2]=>
  &string(5) "Third"
}
array(3) {
  [0]=>
  string(5) "First"
  [1]=>
  string(6) "Second"
  [2]=>
  &string(5) "First"
}
First
array(3) {
  [0]=>
  string(5) "First"
  [1]=>
  string(6) "Second"
  [2]=>
  &string(6) "Second"
}
Second
array(3) {
  [0]=>
  string(5) "First"
  [1]=>
  string(6) "Second"
  [2]=>
  &string(6) "Second"
}
Second

Please note the & symbol that's left in the last array item.

To sum up, whenever you use references in a loop, it's good practice to remove them at the end:

<?php

$collection = array(
    'First',
    'Second',
    'Third',
);

foreach($collection as &$item){
    echo $item . PHP_EOL;
}
unset($item);

var_dump($collection);

foreach($collection as $item){
    var_dump($collection);
    echo $item . PHP_EOL;
}
unset($item);

... prints the expected result every time.

like image 152
Álvaro González Avatar answered Oct 11 '22 09:10

Álvaro González


You should break the reference after the first loop.

foreach($numbers as &$number)
{
   $number = strtoupper($number);
}    
unset($number);

as stated in documentation:

Warning Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().

Also, if you use var_dump() instead of print_r(), you'll notice that the last element of array after the first loop is a reference:

array(4) {
[0]=>
string(4) "ZERO"
[1]=>
string(3) "ONE"
[2]=>
string(3) "TWO"
[3]=>
&string(5) "THREE"
}

If you follow Stefan Gehrig's comments on question, there is a link that perfectly explains this behaviour: http://schlueters.de/blog/archives/141-References-and-foreach.html

like image 26
Snifff Avatar answered Oct 11 '22 09:10

Snifff


variable $number is initialized even after loop , you need to break the reference by unset

this code works properly:

<?php
$numbers = array('zero', 'one', 'two', 'three');

foreach($numbers as &$number)
{
  $number = strtoupper($number);
}

print_r($numbers);
unset($number);

$texts = array();
foreach($numbers as $number)
{
  $texts[] = $number;
}

print_r($texts);
?>

http://www.php.net/manual/en/language.references.unset.php

When you unset the reference, you just break the binding between variable name and variable content. This does not mean that variable content will be destroyed.

...think about this as analogous to the Unix unlink call.

http://uk.php.net/manual/en/control-structures.foreach.php

Warning about foreach

Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().

like image 29
Marek Sebera Avatar answered Oct 11 '22 08:10

Marek Sebera