
Wieder einmal bin ich auf ein Problem gestoßen, über das ich mir stunden-, wenn nicht tagelang den Kopf zerbrach: Ich hatte in PHP eine rekursive Funktion geschrieben, die mit einer foreach
-Schleife ein global deklariertes Array durchlaufen und sich anschließend gegebenenfalls neustarten sollte. Eigentlich lief auch alles einwandfrei – dummerweise aber nur auf meinem lokalen Server. Auf meinem Live-Server dagegen wurde jedesmal, wenn die Funktion sich selbst erneut aufgerufen hatte, die „elterliche“ foreach
-Schleife einfach abgebrochen. D.h., nachdem meine Funktion einen tieferen Level des Arrays erreicht hatte, wurde der höhere Level nicht mehr fortgesetzt. Stattdessen war dann einfach Ende.

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.
Selbst das große weite Internet gab zu diesem Ärgernis ungewöhnlich wenige Informationen her. Aber obwohl ich noch immer keine genaue Erklärung für das Phänomen habe (die PHP-Versionen meines lokalen und des Live-Servers liegen nicht sooo weit auseinander, als daß man es auf die unterschiedlichen Versionen schieben könnte), konnte ich trotzdem zwei Lösungswege finden:
- Man legt einfach eine Kopie des Arrays an und benutzt diese als Argument für die
foreach
-Schleife (nicht ganz so saubere Lösung). - Man schreibt die Funktion so um, daß man ihr die globale Variable als Parameter übergibt, anstatt sie innerhalb der Funktion global zu deklarieren.
Um das Ganze etwas anschaulicher zu machen, habe ich mir mal ein faszinierendes Beispiel mit folgendem Array ausgedacht:
$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]
gibt immer den Namen eines Crewmitgliedes der Enterprise an und $crew[x][1]
die ID des jeweiligen Vorgesetzten. Ich will nun mithilfe einer rekursiven Funktion die Crewmitglieder ihren Vorgesetzten zuordnen. Die Funktion dafür sieht so aus:
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]."
";
enterprise($key);
}
}
}
Das erwartete Ergebnis wäre nun folgendes:
Picard ist Chef von Riker Riker ist Chef von Data Data ist Chef von LaForge Riker ist Chef von Yar Yar ist Chef von Worf Picard ist Chef von Crusher Picard ist Chef von Troi
Lokal klappte das wie gesagt auch, aber mein Online-Webserver spuckte mir immer nur folgendes aus:
Picard ist Chef von Riker Riker ist Chef von Data Data ist Chef von LaForge
Lösungsweg 1
Die erste (schlechtere) Variante sieht nun so aus (nur die Zeilen 3 und 4 haben sich verändert):
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]."
";
enterprise($key);
}
}
}
So läuft die Schleife zwar wieder komplett durch, und das Array wird wieder wie gewünscht ausgelesen, aber mal angenommen, man hätte in seinem Array nun einen riesigen Kategoriebaum gespeichert – dann wäre es ja performancetechnisch gesehen keine gute Idee, es unnötigerweise zu kopieren. Daher empfehle ich
Lösungsweg 2
Das globale Array wird der Funktion beim Aufruf übergeben. Auch so läuft die Schleife brav durch, und man muß das Array nicht kopieren.
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]."
";
enterprise($crew, $key);
}
}
}
enterprise($crew);
Der Parameter stört?
Nun gab es in meinem Fall noch eine Besonderheit: Die Funktion enterprise()
sollte unbedingt parameterfrei sein. (Sie sollte später als eine Art Template-Tag dienen.) Um das zu erreichen, habe ich die eigentliche Funktion umbenannt und mir anschließend eine „Dummy-Funktion“ namens enterprise()
gebaut:
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]."
";
enterprise_crew($crew, $key);
}
}
}
function enterprise() {
global $crew;
enterprise_crew($crew);
}
Jetzt kann man enterprise()
wieder ohne Parameter aufrufen.
Ich habe keine Ahnung, ob dieser Post jetzt überhaupt irgendwem weiterhilft – es müssen ja fünf Bedingungen erfüllt sein, damit dieses Problem überhaupt auftritt (PHP, Rekursion, Schleife, globales Array, schlecht konfigurierter Server).
Naja, ansonsten wird mir dieser Post eben nur als Gedächtnisstütze dienen. ;)
Ich wüßte aber immer noch gerne, welche Servereinstellung dafür verantwortlich ist, daß es auf dem einen Server klappt und auf dem anderen nicht. Vielleicht weiß das irgendwer?
[:] [:en]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:
- Simply make a copy of the array and use that as the argument for the
foreach
loop (not the cleanest solution). - 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:
function enterprise($crewmember = 0) {
global $crew;
foreach($crew as $key => $member) {
if($member[1] == $crewmember && $key != 0) {
echo $crew[$crewmember][0]." is the boss of ".$member[0]."
";
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):
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]." is the boss of ".$member[0]."
";
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.
function enterprise($crew, $crewmember = 0) {
foreach($crew as $key => $member) {
if($member[1] == $crewmember && $key != 0) {
echo $crew[$crewmember][0]." is the boss of ".$member[0]."
";
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()
:
function enterprise_crew($crew, $crewmember = 0) {
foreach($crew as $key => $member) {
if($member[1] == $crewmember && $key != 0) {
echo $crew[$crewmember][0]." is the boss of ".$member[0]."
";
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?
[:]
4 Antworten zu “[:de]PHP + Rekursion + Schleife + globales Array = Ärger[:en]PHP + Recursion + loop + global array = trouble[:]”
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
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
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.
Thanks a lot for this post. It helped me when I couldn’t find the solution anywhere else on the internet.