Deutsch English
Programming Diesen Artikel auf Deutsch lesen

PHP + Recursion + loop + global array = trouble

Recursion

Recursion

Once again, I came across an issue that I racked my brain about for hours, if not days: I had written a recursive function in PHP that should run through a globally declared array with a foreach loop and then restart itself if necessary. Everything ran perfectly well – but only on my local server. On my live server, however, every time the function called itself again, the “parent” foreach loop was simply canceled. Meaning that, after my function had reached a deeper level of the array, the higher level was not being continued. Instead, it just quit there.

Even the big wide internet provided an unusually small amount of information about this annoyance. But even though I still don’t have an exact explanation for the phenomenon (the PHP versions of my local and the live server are not that far apart, so I can’t blame it on the different versions), I was able to find two solutions:

  1. Simply make a copy of the array and use that as the argument for the foreach loop (not the cleanest solution).
  2. Rewrite the function so that you pass the global variable to it as a parameter, rather than declaring it globally within the function.

To make things a little clearer, I have devised a fascinating example with the following array:

$crew = array(
	0 => array('Picard',	0),
	1 => array('Riker',	0),
	2 => array('Data',	1),
	3 => array('Yar',	1),
	4 => array('LaForge',	2),
	5 => array('Worf',	3),
	6 => array('Crusher',	0),
	7 => array('Troi',	0)
);

$crew[x][0] always contains the name of a crew member of the Enterprise, and $crew[x][1] the ID of the respective superior. I now want to assign the crew members to their superiors with the help of a recursive function. The function looks like this:

1
2
3
4
5
6
7
8
9
10
function enterprise($crewmember = 0) {
	global $crew;
 
	foreach($crew as $key => $member) {
		if($member[1] == $crewmember && $key != 0) {
			echo $crew[$crewmember][0]." ist Chef von ".$member[0]."<br />";
			enterprise($key);
		}
	}
}

The expected result would be the following:

Picard is the boss of Riker
Riker is the boss of Data
Data is the boss of LaForge
Riker is the boss of Yar
Yar is the boss of Worf
Picard is the boss of Crusher
Picard is the boss of Troi

As I said, this worked fine locally, but my online server always spit out the following:

Picard is the boss of Riker
Riker is the boss of Data
Data is the boss of LaForge

Solution 1

The first (worse) version looks like this (only lines 3 and 4 have changed):

1
2
3
4
5
6
7
8
9
10
function enterprise($crewmember = 0) {
	global $crew;
	$crew_copy = $crew;
	foreach($crew_copy as $key => $member) {
		if($member[1] == $crewmember && $key != 0) {
			echo $crew[$crewmember][0]." ist Chef von ".$member[0]."<br />";
			enterprise($key);
		}
	}
}

This way, the loop runs through completely, and the array is being read as desired again, but suppose you have stored a huge category tree in your array – then it would, performance-wise, not be a good idea to copy it unnecessarily. I therefore recommend

Solution 2

The global array is being passed to the function when calling. Thus, the loop also runs through dutifully, and you don’t have to copy the array.

1
2
3
4
5
6
7
8
9
function enterprise($crew, $crewmember = 0) {
	foreach($crew as $key => $member) {
		if($member[1] == $crewmember && $key != 0) {
			echo $crew[$crewmember][0]." ist Chef von ".$member[0]."<br />";
			enterprise($crew, $key);
		}
	}
}
enterprise($crew);

Don’t like the parameter?

Now, in my case, there was another speciality: The enterprise() function should not have any parameters. (It was meant to serve as a kind of template tag later.) To achieve this, I renamed the actual function and then built a “dummy function” called enterprise():

1
2
3
4
5
6
7
8
9
10
11
12
13
function enterprise_crew($crew, $crewmember = 0) {	
	foreach($crew as $key => $member) {
		if($member[1] == $crewmember && $key != 0) {
			echo $crew[$crewmember][0]." ist Chef von ".$member[0]."<br />";
			enterprise_crew($crew, $key);
		}
	}
}
 
function enterprise() {
	global $crew;
	enterprise_crew($crew);
}

Now you can call enterprise() without a parameter again.

I have no idea if this post will help anyone at all – there are indeed five conditions to be met in order for this problem to occur at all (PHP, recursion, loop, global array, poorly configured server).
Well, otherwise this post will only serve me as a reminder. ;)

But I’d still like to know which server setting is responsible so it works on one server, and on the other it doesn’t. Perhaps someone knows?



Comments

  1. 12th March 2017
    9:22 pm

    Sanket flag

    Thanks a lot for this post. It helped me when I couldn’t find the solution anywhere else on the internet.

  2. 10th November 2013
    6:27 pm

    Ginchen flag

    No, I haven’t had register_globals turned on in 10 years, I think. ;) It’s always one of the first things I make sure to turn off.

    I think it was just a buggy version of PHP that caused this problem. On my local server I had a different version installed, so the problem didn’t occur. But I couldn’t change the version on the live server, since it was a shared web hosting server; I just had to use that buggy version.

  3. 10th November 2013
    1:47 am

    Markus flag

    Thanks – you made my day (well.. my night).
    I ran in to the same problem and solved it the oop way.

    I was wondering if you have register globals on on your local server. That

  4. 7th October 2012
    2:18 am

    Ralf flag

    Hi, ich hänge an einen ähnlichen Problem (oder dem Selben ?) .

    Schau ma unter: http://www.selfphp.info/praxisbuch/praxisbuchseite.php?site=102&group=22&page=2

    Normale Variablen verlieren beim Verlassen des lokalen Kontextes ihren Wert und werden beim Wiedereintritt neu initialisiert…

    Grüße, Ralf

Write comment

Allowed HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <p> <pre lang="" line="" escaped=""> <q cite=""> <strike> <strong> <img src="" alt="" class="" width="" height=""> | Code snippets can be posted in `backticks`. Example: `<?php echo "Hi!"; ?>`