Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP recursive functions work differently in different versions?

Tags:

php

This is driving me crazy. Recursive functions seem to work differently in 5.4.4 and 5.1.6 (hosting server of a client over which I have no control). I can't really explain it except by example:

<?php
$simpsons[0] = array("name"=>"Abe","parent"=>-1);
$simpsons[1] = array("name"=>"Homer","parent"=>0); // Homer's parent is Abe
$simpsons[2] = array("name"=>"Bart","parent"=>1); // Bart's parent is Homer
$simpsons[3] = array("name"=>"Lisa","parent"=>1); // Lisa's parent is Homer
$simpsons[4] = array("name"=>"Maggie","parent"=>1); // Maggie's parent is Homer


function get_children($parent) {
    global $simpsons;

    foreach ($simpsons as $index=>$onesimpson) {
        if ($onesimpson["parent"]==$parent) {
            echo "$onesimpson[name] is a child of ".$simpsons[$parent]["name"].".<br />\n";
            get_children($index);
        }
    }
}

get_children(0);
?>

On PHP 5.4.4 the output is

Homer is a child of Abe.
Bart is a child of Homer.
Lisa is a child of Homer.
Maggie is a child of Homer.

while on PHP 5.1.6 the output is

Homer is a child of Abe.
Bart is a child of Homer.

I'm not good with terminology so I can't explain what's happening (it's like in 5.1.6 the called function changes the parameter of the calling function even when the called function finishes), but I've tested this in PHP sandbox online on these two versions and the problem is identical - it's not specific to my setup or the hosting server setup.

like image 864
L84 Avatar asked Aug 21 '13 16:08

L84


2 Answers

I'm not sure what changed to make that start working in 5.2, but an array only has one internal pointer (that is what is used by foreach), so when you use a global array like that the result you see in versions up to 5.2 make a lot of sense. You start a foreach loop, the internal pointer advances, then you recursively call get_children, start another foreach loop and the internal pointer resets and then iterates through the array.

When you return to the callee, the internal pointer will be at the end of the array already and the foreach loop will complete. To quote the manual:

As foreach relies on the internal array pointer changing it within the loop may lead to unexpected behavior.

Using foreach within a foreach on the same array is an example of that.

Edit I found a couple of relevant bug reports that were marked fixed in version 5.2.1:

  • https://bugs.php.net/bug.php?id=35106
  • https://bugs.php.net/bug.php?id=26396

It turns out that foreach works on a clone of the array, so nesting foreach loops is perfectly valid and this was indeed a bug, where array references were not cloned in foreach loops) up until version 5.2.1.

like image 116
Paul Avatar answered Oct 25 '22 04:10

Paul


I have tweaked your code a little bit. Apparently when you pass the $simpsons array reference as a parameter to your recursive function, it works in all versions.

$simpsons = array();
$simpsons[0] = array("name"=>"Abe","parent"=>-1);
$simpsons[1] = array("name"=>"Homer","parent"=>0); // Homer's parent is Abe
$simpsons[2] = array("name"=>"Bart","parent"=>1); // Bart's parent is Homer
$simpsons[3] = array("name"=>"Lisa","parent"=>1); // Lisa's parent is Homer
$simpsons[4] = array("name"=>"Maggie","parent"=>1); // Maggie's parent is Homer


function get_children($simpsons, $parent) {
  foreach ($simpsons as $index=>$onesimpson) {
    if ($onesimpson["parent"]==$parent) {
      echo "$onesimpson[name] is a child of ".$simpsons[$parent]["name"].".<br />\n";
      get_children($simpsons, $index);
    }
  }
}

get_children($simpsons, 0);
like image 26
MurifoX Avatar answered Oct 25 '22 05:10

MurifoX