Thoughts on website ideas, PHP and other tech topics, plus going car-free
Seriously fiendish PHP gotcha
Categories: PHP

Yesterday I was debugging a legacy PHP app, and came across this item of code. This example is substantially condensed for brevity:

foreach ( $tree as $key => &$branch ) {
    if ( isset( $tree[ $key+1 ] ) && isset( $branch['node'] ) ) {
        $tree[ $key+1 ][ 'selectId' ] = $branch['node']->Data->code;
        $tree[ $key+1 ][ 'children' ][ $branch['node']->Data->code ]['selected'] = true;

# Menu should appear in reverse
$tree = array_reverse( $tree );

# OK, the bug is exhibited here
foreach ( $tree as $branch ) {
    echo 'Checksum: ' . sha1( print_r( $tree, true ) ) . '<br/>';

OK, here’s the output:

Checksum: 3ca7d13b26a3382c901fdd0d3fa606439167e18d
Checksum: 14279be5bd764b559922a1f6f65d81dd78c1e7d4
Checksum: f8b38a67d18dee92e25a0e0896e8f1a526c2915b

On first examination, this doesn’t look right at all; after all, iterating over an array with foreach should not modify the array. I discovered, looking at the output of print_r(), that it was the first element of $tree that was being modified upon each iteration.

(The answer comes after this paragraph, so developers wanting to work it out should stop reading :)).

As it turns out, the bug is not in the second loop; it’s in the first one. The variable $branch has been set up as a reference instead of the usual copy, using the ampersand operator — and a quick look inside that loop shows that this is unnecessary. Foreach loops will normally leave the element variable containing the value of the last element, but in this case, $branch is left as an active pointer to the last element. This pointer even survives array_reverse(), which results finally in $branch pointing to the first element of $tree.

Hence, when the second loop executes, every time $branch is written to, it overwrites the first element of the array. To fix this, it would have been sufficient to unset($branch) after the first loop, or better yet, to avoid the reference operator entirely.

Leave a Reply