Deutsch English
Programmierung Read this post in English

PHP + Rekursion + Schleife + globales Array = Ärger

Rekursion

Rekursion

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.

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:

  1. Man legt einfach eine Kopie des Arrays an und benutzt diese als Argument für die foreach-Schleife (nicht ganz so saubere Lösung).
  2. 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:

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);
		}
	}
}

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):

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);
		}
	}
}

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.

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);

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:

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);
}

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?



Kommentare

  1. 10. November 2013
    18:27 Uhr

    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.

  2. 10. November 2013
    1:47 Uhr

    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

  3. 7. Oktober 2012
    2:18 Uhr

    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

Kommentieren

Erlaubtes 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=""> | Codeschnipsel können in `backticks` gepostet werden. Beispiel: `<?php echo "Hi!"; ?>`