Verschachtelte Funktionen in PHP und warum du sie vermeiden solltest!

Diese Woche habe ich den Code einer Website überprüft. Die Website verwendete einige Code-Schnipse und in einem davon habe ich eine verschachtelte PHP-Funktion gefunden. Der Code funktionierte nicht wie erwartet. Nicht wegen der verschachtelten Funktion, aber er hat mich auf die Idee für diesen Blogbeitrag gebracht. Ich kann den ursprünglichen Code nicht teilen, aber ich hoffe, ich kann euch anhand eines Beispiels zeigen, wie verschachtelte Funktionen funktionieren und warum ihr sie wahrscheinlich nicht verwenden solltet.

Was ist eine verschachtelte Funktion?

Eine verschachtelte Funktion in PHP ist eine Funktion, die im Inhalt einer anderen Funktion deklariert wird. Wir könnten sie als „äußere Funktion“ und „innere Funktion“ oder „verschachtelte Funktion“ bezeichnen. Hier ist ein Beispiel für eine solche verschachtelte Funktion:

function multiplyAllByTwo( $array ) {
	function multiplyByTwo( $value ) {
		return $value * 2;
	}

	return array_map( 'multiplyByTwo', $array );
}

Wir haben eine Funktion, um ein Array zu verarbeiten und alle Werte darin mit 2 zu multiplizieren. Durch die Verwendung einer Funktion könnten wir „potenziell“ dasselbe für mehrere Arrays machen. Innerhalb dieser Funktion deklarieren wir eine andere Hilfsfunktion, die einen gegebenen Wert mit 2 multipliziert. Das ist die verschachtelte Funktion. Sie wird dann mit array_map() verwendet, die sie dann auf jeden Eintrag des Arrays anwendet. Wenn wir also ein Array übergeben, erhalten wir das Array zurück, in dem alle Werte mit 2 multipliziert wurden.

$inputArray = [ 1, 2, 3, 4, 5 ];
$resultArray = multiplyAllByTwo( $inputArray );
print_r( $resultArray );
/*
Array
(
	[0] => 2
    [1] => 4
    [2] => 6
    [3] => 8
    [4] => 10
)
*/

Das ist toll, also wo liegt das Problem? Versuchen wir einfach mal, das resultierende Array erneut zu multiplizieren:

$inputArray   = [ 1, 2, 3, 4, 5 ];
$resultArray  = multiplyAllByTwo( $inputArray );
$resultArray2 = multiplyAllByTwo( $resultArray );
print_r( $resultArray );
print_r( $resultArray2 );

Was erwarten nach den beiden Aufrufen? Ein zweites Array, bei dem alle Werte erneut mit 2 multipliziert wurden, richtig? Aber was erhalten wir stattdessen?

PHP Fatal error:  Cannot redeclare multiplyByTwo() ...

Wenn die Funktion ein zweites Mal aufgerufen wird, erhalten wir einen schwerwiegenden Fehler, da die Funktion nicht erneut mit demselben Namen deklariert werden kann. Eine verschachtelte Funktion funktioniert also nur für eine „äußere Funktion“, die nur einmal ausgeführt wird. Was könnten wir also stattdessen tun?

Verwende keine verschachtelte Funktion

Der einfachste Weg wäre es, die verschachtelte Funktion aus der anderen Funktion zu entfernen und sie im globalen Namespace zu deklarieren.

function multiplyAllByTwo( $array ) {
	return array_map( 'multiplyByTwo', $array );
}

function multiplyByTwo( $value ) {
	return $value * 2;
}

Jetzt können die (vorher) äußere Funktion zweimal aufrufen:

$inputArray = [ 1, 2, 3, 4, 5 ];
$resultArray1 = multiplyAllByTwo( $inputArray );
$resultArray2 = multiplyAllByTwo( $resultArray1 );
print_r( $resultArray1 );
print_r( $resultArray2 );
/*
Array
(
	[0] => 2
    [1] => 4
    [2] => 6
    [3] => 8
    [4] => 10
)
Array
(
	[0] => 4
    [1] => 8
    [2] => 12
    [3] => 16
    [4] => 20
)
*/

Jetzt erhalten wir das gewünschte Ergebnis. Aber wir füllen auch den globalen Namespace mit vielen Funktionen und müssen dabei sicherstellen, dass wir für diese verschiedenen Hilfsfunktionen nicht denselben Funktionsnamen verwenden. Als Alternative kannst du die Funktion auch einer Variable zuweisen und diese anstelle des Funktionsnamen-Strings verwenden. Aber dazu müsstest du diese Variable wieder innerhalb der äußeren Funktion zuweisen, da sie sonst nicht verfügbar wäre. Oder du müsstest die Variable innerhalb der Funktion mit dem globalen Schlüsselwort verfügbar machen. Beide Lösungen sind nicht wirklich schön, und deshalb möchte ich nicht einmal Code-Snippets dafür zeigen ?

Also, wenn du nicht wirklich eine global deklarierte Funktion benötigst, wie kannst du es dann sonst lösen? Es gibt einen anderen Weg.

Verwende eine anonyme Funktion

Eine anonyme Funktion wird oft in Kombination mit Funktionen wie array_map() und ähnlichen Funktionen verwendet. Unser Code würde dann so aussehen:

function multiplyAllByTwo( $array ) {
	return array_map( function ( $value ) {
		return $value * 2;
	}, $array );
}

In diesem Beispiel deklarieren wir die Funktion in dem Moment, in dem wir sie benötigen. Dadurch entfällt auch die Notwendigkeit, einen schönen Namen dafür zu finden, und wir wissen alle, dass das Benennen von Dingen eine der „zwei schwierigen Dinge in der Programmierung“ ist ?

Mit PHP 7.4 und höher kannst du sogar eine schöne kleine Arrow-Function verwenden, die es dir ermöglicht, einen Einzeiler für dieses Snippet zu schreiben:

function multiplyAllByTwo( $array ) {
	return array_map( fn( $value ) => $value * 2, $array );
}

Sieht gut aus, oder?

Fazit: Wann sollte man was verwenden?

Ich würde empfehlen, niemals eine verschachtelte Funktion zu verwenden! Obwohl das in anderen Programmiersprachen häufig gemacht wird, kann es in PHP leicht zu schwerwiegenden Fehlern und Problemen beim Testen und Debuggen führen.

Wenn es sein kann, dass du die Logik der „inneren Funktion“ für mehrere „äußere Funktionen“ benötigst, dann deklariere die Funktion immer mit einem Namen, entweder im globalen Namespace oder in einer PHP-Klasse.

Wenn du diese Logik nur für diese spezifische „äußere Funktion“ benötigst oder sie wirklich sehr einfach ist, wie in diesem Beispiel, kannst du eine anonyme Funktion oder sogar eine Arrow-Function verwenden.

Veröffentlicht von

Bernhard ist fest angestellter Webentwickler, entwickelt in seiner Freizeit Plugins, schreibt in seinem Blog über WordPress und andere Themen, treibt sich gerne bei den WP Meetups in Berlin und Potsdam herum und läuft nach Feierabend den ein oder anderen Halbmarathon.

1 Kommentar » Schreibe einen Kommentar

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert