Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

php issue with looping over an array twice using foreach and passing value by reference

I need to loop through an array twice - once to modify its values, and once to display its content in html. Unfortunately, I am running into trouble. I have created a test scenario to illustrate the issue.

$cases = array(
  array('caseStyle' => 'case style 1', 'caseNum' => 'case01'),
  array('caseStyle' => 'case style 2', 'caseNum' => 'case02'),
  array('caseStyle' => 'case style 3', 'caseNum' => 'case03'),
  array('caseStyle' => 'case style 4', 'caseNum' => 'case04'),
  array('caseStyle' => 'case style 5', 'caseNum' => 'case05'),
  array('caseStyle' => 'case style 6', 'caseNum' => 'case06')
);

foreach ($cases as $k => &$v) {
    $v['caseNum'] = ucwords($v['caseNum']);
}

foreach ($cases as $k => $v) {
    echo $v['caseNum'] . ' - ' . $v['caseStyle'] . '<br/>';
}

This outputs:

Case01 - case style 1
Case02 - case style 2
Case03 - case style 3
Case04 - case style 4
Case05 - case style 5
Case05 - case style 5

Note the values for the last item are wrong.

If during the second iteration I use foreach($cases as $k => $d) instead of foreach($cases as $k => $v) or if I unset $v before the second iteration (unset($v)) everything goes fine. This is intriguing. What am I missing?

like image 711
longwing Avatar asked Sep 07 '13 03:09

longwing


2 Answers

When you execute a foreach loop, the variables in the () persist after the loop is done. That means that after the first loop finishes, you can var_dump($v) and get the values that were contained in it after the last iteration through the first loop. This is the case whether it's a reference (&$v) or a normal variable ($v).

However, if it's a reference in the first loop, it remains a reference unless it's unset. That means when you enter the second loop, you're overwriting the reference with the value of the array element you're currently looking at.

Remember, what foreach($cases as $k => $v) really means is "take the key for this element in $cases and assign that to $k, and take the value for this element and assign it to $v)". Since $v is still a reference to the last element in the array, rather than setting the value of a new variable $v, you're actually updating the value of where $v already points to.

What that means is, if we simplify $cases to be simply ['a', 'b', 'c', 'd'], after the first time through the second foreach, $cases is now ['a', 'b', 'c', 'a'] because you've reassigned the element in $cases that $v points to - the last one - to have the same value as the first one. The second time through, it's ['a', 'b', 'c', 'b']. The third time through it's ['a', 'b', 'c', 'c']. Then, the last time through, you're assigning it to itself, and at that time it holds the value 'c'.

This is really just a case of php working as expected. The solution is to unset($v) as soon as the first loop finishes, to make sure that the next time you use $v you're using a new variable, rather than an existing reference.

To see this in action:

Head on over to http://phpfiddle.org/ and paste the following code, and run it; you'll see in the output that $v is maintained after the first loop completes, and that the value of $cases[5] changes each time through the second loop.

$cases = array(
  array('caseStyle' => 'case style 1', 'caseNum' => 'case01'),
  array('caseStyle' => 'case style 2', 'caseNum' => 'case02'),
  array('caseStyle' => 'case style 3', 'caseNum' => 'case03'),
  array('caseStyle' => 'case style 4', 'caseNum' => 'case04'),
  array('caseStyle' => 'case style 5', 'caseNum' => 'case05'),
  array('caseStyle' => 'case style 6', 'caseNum' => 'case06')
);

foreach ($cases as $k => &$v) {
    $v['caseNum'] = ucwords($v['caseNum']);
}
var_dump($v);
echo "<br />";
foreach ($cases as $k => $v) {
    print_r($cases);
    echo "<br />";
    echo $k . ': ' . $v['caseNum'] . ' - ' . $v['caseStyle'] . '<br/>';
}
like image 192
DiMono Avatar answered Nov 12 '22 15:11

DiMono


Try this:

foreach ($cases as $k => &$v) {
    $cases[$k]['caseNum'] = ucwords($v['caseNum']);
    echo $cases[$k]['caseNum'] . ' - ' . $cases[$k]['caseStyle'] . '<br/>';
}
like image 31
undone Avatar answered Nov 12 '22 16:11

undone