Abschnitt 6 · Lektion 6
Anonyme Funktionen
In Lektion 5.4 hast du zum ersten Mal eine Funktion als Argument übergeben, damals nur mit dem Nötigsten dazu. Genau darum geht es hier: Funktionen, die nirgends stehen bleiben, weil sie nur an einer einzigen Stelle gebraucht werden.
Eine Funktion, die keinen Namen hat
<?php
declare(strict_types=1);
// Mit Namen, wie bisher:
function verdopple(int $z): int
{
return $z * 2;
}
// Ohne Namen, dafuer in einer Variablen:
$verdreifache = function (int $z): int {
return $z * 3;
};
echo verdopple(7), "\n";
echo $verdreifache(7), "\n";
// Was steckt in der Variablen?
echo get_debug_type($verdreifache), "\n"; Nach function folgt bei einer anonymen Funktion direkt die Klammer. Der Name fehlt, und dafür
steht am Ende ein Semikolon, denn das Ganze ist eine Zuweisung wie jede andere.
In der Variablen liegt ein Objekt vom Typ Closure. Aufrufen kannst du es wie eine Funktion, indem
du die Klammern hinter den Variablennamen setzt.
Der eigentliche Zweck ist aber nicht die Variable. Er ist, dass du eine Funktion irgendwohin
übergeben kannst: an usort, an array_map, an array_filter. Diese Funktionen wollen ja gar
keinen Namen wissen, sie wollen wissen, was zu tun ist.
Auch hier gilt Lektion 6.4
<?php
declare(strict_types=1);
$grenze = 200;
$obst = [
["name" => "Apfel", "preis" => 190],
["name" => "Kirsche", "preis" => 340],
["name" => "Zitrone", "preis" => 90],
];
// Ohne use kennt der Koerper $grenze nicht:
$ohne = array_filter($obst, function (array $o): bool {
return $o["preis"] > $grenze;
});
echo "ohne use: ", count($ohne), " uebrig\n";
// Mit use kommt der Wert herein:
$mit = array_filter($obst, function (array $o) use ($grenze): bool {
return $o["preis"] > $grenze;
});
echo "mit use: ", count($mit), " uebrig\n"; Die erste Zeile ist die interessante. ohne use: 3 uebrig heißt: Es ist nichts herausgefallen,
obwohl die Grenze bei 200 liegt und zwei Preise darunter sind.
Der Grund ist derselbe wie in der Lektion davor. Der Körper der anonymen Funktion sieht $grenze
nicht, obwohl sie weiter oben in derselben Datei steht. Er bekommt null, und jeder Preis ist größer als
nichts. Auf dem Fehlerkanal stehen dazu drei Warnungen, eine je Durchgang, und
php index.php; echo $? meldet trotzdem eine 0.
use ($grenze) hinter der Parameterliste holt den Wert herein. Damit stimmt die zweite Zeile.
Mehrere Werte trennst du mit Komma.
Die kurze Form spart sich das
<?php
declare(strict_types=1);
$grenze = 200;
$obst = [
["name" => "Apfel", "preis" => 190],
["name" => "Kirsche", "preis" => 340],
["name" => "Zitrone", "preis" => 90],
];
// Lange Form:
$lang = array_filter($obst, function (array $o) use ($grenze): bool {
return $o["preis"] > $grenze;
});
// Kurze Form, holt sich $grenze von selbst:
$kurz = array_filter($obst, fn(array $o): bool => $o["preis"] > $grenze);
echo count($lang), " und ", count($kurz), "\n";
$namen = array_map(fn(array $o): string => $o["name"], $kurz);
echo implode(", ", $namen), "\n"; fn statt function, ein Doppelpfeil statt der geschweiften Klammern, und kein use. Eine
Pfeilfunktion holt sich alles, was sie braucht, von selbst aus ihrer Umgebung. Beide Zeilen im
Beispiel liefern deshalb dasselbe Ergebnis.
Dafür gibt es eine Einschränkung: In eine Pfeilfunktion passt genau ein Ausdruck. Kein
Semikolon, keine zweite Zeile, kein if mit geschweiften Klammern. Was hinter dem Doppelpfeil
steht, ist automatisch der Rückgabewert; ein return schreibst du nicht dazu.
Die Faustregel ist damit einfach: Passt es in eine Zeile, nimm fn. Braucht die Funktion
mehrere Schritte, nimm die lange Form und schreib das use dazu.
Womit die Funktion rechnet, steht früh fest
<?php
declare(strict_types=1);
$n = 1;
$mitUse = function () use ($n): int {
return $n;
};
$pfeil = fn(): int => $n;
// Beide werden erst jetzt aufgerufen, nach der Aenderung:
$n = 2;
echo $mitUse(), "\n";
echo $pfeil(), "\n";
echo $n, "\n"; Beide Funktionen geben 1 zurück, obwohl $n vor dem Aufruf auf 2 gesetzt wurde.
use ($n) nimmt eine Kopie, und zwar in dem Moment, in dem die anonyme Funktion aufgeschrieben
wird, nicht wenn sie läuft. Bei fn ist es genauso. Das ist meistens genau, was man will: Die
Funktion soll mit dem Wert rechnen, der zum Zeitpunkt ihrer Entstehung galt.
Der Reiter Debug zeigt die zwei Werte nebeneinander. Nach Aufzeichnen steht an der
echo-Zeile draußen n=2, an der return-Zeile in $mitUse direkt darunter n=1. Zwei
Variablen mit demselben Namen und verschiedenen Werten, und genau das ist die Kopie. Die
Pfeilfunktion taucht in der Liste nicht auf: Sie hat keine Anweisung, nur einen Ausdruck.
Es gäbe auch use (&$n) mit dem kaufmännischen Und, dann arbeitet sie mit dem Original. Dafür gilt
dasselbe wie für den Referenzparameter aus Lektion 6.4: Du erkennst es, wenn du es siehst, und
schreibst es selbst nicht hin.
Der häufigste Einsatz
<?php
declare(strict_types=1);
$richtung = -1;
$obst = [
["name" => "Apfel", "preis" => 190],
["name" => "Banane", "preis" => 90],
["name" => "Kirsche", "preis" => 280],
];
usort($obst, function (array $a, array $b) use ($richtung): int {
return $richtung * ($a["preis"] <=> $b["preis"]);
});
foreach ($obst as $frucht) {
echo $frucht["name"], ": ", $frucht["preis"], "\n";
} usort aus Lektion 5.5 will eine Funktion, die zwei Einträge vergleicht und eine Zahl zurückgibt:
negativ, wenn der erste vorne stehen soll, positiv, wenn der zweite vorne stehen soll, und 0 bei
Gleichstand. Der Raumschiff-Operator <=> aus Lektion 3.6 liefert genau das.
Hier steht die lange Form, damit du das use einmal an einer echten Aufgabe siehst; ein fn täte
es auch. Mit -1 dreht sich das Ergebnis um, aus aufsteigend wird absteigend, und du musst dafür
keine zweite Vergleichsfunktion schreiben. Genau dafür ist use da: Eine anonyme Funktion, die von
außen einen Wert bekommt, ist kein starrer Block mehr, sondern eine Regel, die du einstellen kannst.
Zum Mitnehmen
In Lektion 5.4 stand schon ein fn($betrag) => $betrag / 100 in einem array_map, mit dem Versprechen, dass die Erklärung später kommt. Hier ist sie. Es geht um Funktionen, die keinen Namen haben, weil sie nur an einer einzigen Stelle gebraucht werden.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 5 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.