Generatoren Generatoren-Übersicht Generatoren bieten eine einfache Möglichkeit, um einfache Iteratoren zu erstellen, ohne den Overhead oder die Komplexität der Erstellung einer Klasse zu haben, die das Iterator-Interface implementiert. Mit einem Generator können auf bequeme Weise Daten für &foreach; -Schleifen bereitgestellt werden, ohne ein Array im Speicher zu erzeugen, was dazu führen kann, dass das Programm ein Speicherlimit überschreitet oder beträchtliche Prozessorzeit benötigt. Alternativ kann eine Generatorfunktion verwendet werden, die einer normalen Funktion entspricht, bei der aber keine einmalige Rückgabe erfolgt, sondern der Generator so oft wie nötig einen Wert abgibt (Stichwort: &yield;), um die Werte zu liefern, über die iteriert werden soll. Wie bei Iteratoren ist ein wahlfreier Datenzugriff nicht möglich. Ein einfaches Beispiel dazu ist, die range-Funktion durch einen Generator neu zu implementieren. Die Standard-range-Funktion generiert und liefert Arrays, welche jeden Wert enthalten, was große Arrays zur Folge haben kann: zum Beispiel hat der Aufruf range(0, 1000000) zur Folge, dass weit über 100 MB an Speicher benötigt werden. Als Alternative können wir einen xrange()-Generator implementieren, welcher immer nur genug Speicher benötigt, um ein Iterator-Objekt zu erzeugen und intern den aktuellen Zustand des Generators zu verfolgen, was sich als weniger als 1 Kilobyte herausstellt. Implementierung von <function>range</function> als Generator = 0) { throw new LogicException('Schrittweite muss negativ sein'); } for ($i = $start; $i >= $limit; $i += $step) { yield $i; } } } /* * Hinweis: sowohl range() als auch xrange() * erzeugen die gleiche Ausgabe. */ echo 'Einstellige ungerade Zahlen von range(): '; foreach (range(1, 9, 2) as $zahl) { echo "$zahl "; } echo "\n"; echo 'Einstellige ungerade Zahlen von xrange(): '; foreach (xrange(1, 9, 2) as $zahl) { echo "$zahl "; } ?> ]]> &example.outputs; <classname>Generator</classname>-Objekte Beim Aufruf einer Generatorfunktion wird ein neues Objekt der internen Generator-Klasse zurückgegeben. Dieses Objekt implementiert das Iterator-Interface in gleicher Weise wie es ein forward-only Iterator-Objekt machen würde und stellt Methoden zur Verfügung, die aufgerufen werden können, um den Zustand des Generators zu manipulieren, einschließlich des Sendens von Werten an, und der Rückgabe von Werten von ihm. Generator-Syntax Eine Generatorfunktion sieht genau so aus wie eine normale Funktion mit der Ausnahme, dass ein Generator statt eines Wertes so viele Werte wie nötig zurückgibt (Stichwort: &yield;). Jede Funktion, die &yield; enthält, ist eine Generatorfunktion. Wenn eine Generatorfunktion aufgerufen wird, wird ein Objekt zurückgegeben, über das iteriert werden kann. Wenn Sie über dieses Objekt iterieren (zum Beispiel, per &foreach;-Schleife), wird PHP die Iteratorfunktionen des Objekts jedes Mal aufrufen, wenn ein Wert benötigt wird. Dann wird der Status des Generators gesichert, so dass fortgefahren werden kann, wenn der nächste Wert benötigt wird. Sobald keine weiteren Werte zurückgegeben werden können, kann der Generator einfach zurückgeben, und der rufende Code wird fortgesetzt, als gäbe es keine weiteren Werte in einem Array. Ein Generator kann Werte zurückgeben, die unter Verwendung von Generator::getReturn ermittelt werden können. <command>yield</command>-Schlüsselwort Das Herz einer Generatorfunktion ist das yield-Schlüsselwort. In seiner einfachsten Form sieht das yield-Schlüsselwort wie eine return-Anweisung aus, ausser dass die Ausführung mit der Rückgabe nicht beendet wird, sondern yield stattdessen bei der Schleife über den Generator einen Wert für den Code bereitstellt und die Ausführung der Generatorfunktion anhält. Ein einfaches Beispiel zum liefern (yielding) von Werten ]]> &example.outputs; Intern werden sequentielle Integer-Schlüssel mit den abgelieferten Werten verknüpft, so wie mit einem nicht-assoziativen Array. Produzieren von Werten mit Schlüsseln PHP unterstützt ebenfalls assoziative Arrays, und Generatoren unterscheiden sich nicht davon. Als Ergänzung zum Produzieren einfacher Werte, wie oben gezeigt, können Sie zur gleichen Zeit auch einen Schlüssel liefern. Die Syntax für das Produzieren eines Schlüssel/Wert-Paares ist sehr ähnlich wie die Definition von assoziativen Arrays, wie unten gezeigt. Produzieren eines Schlüssel/Wert-Paares $felder; } } foreach (eingabe_parser($eingabe) as $id => $felder) { echo "$id:\n"; echo " $felder[0]\n"; echo " $felder[1]\n"; } ?> ]]> &example.outputs; Produzieren von null-Werten Yield kann ohne Argument aufgerufen werden, um einen &null;-Wert mit einem automatischen Schlüssel zurückzugeben. Produzieren von &null;s ]]> &example.outputs; NULL [1]=> NULL [2]=> NULL } ]]> Produzieren als Referenz Generatorfunktionen sind genauso in der Lage Werte als Referenz zurückzugeben, wie als Wert. Dies kann in gleicher Weise erfolgen, wie beim Zurückgeben von Referenzen aus Funktionen: dies geschieht, indem dem Funktionsnamen ein Kaufmanns-Und vorangestellt wird. Produzieren von Werten als Referenz 0) { yield $wert; } } /* * Hinweis: wir können $nummer innerhalb der Schleife ändern, * und weil der Generator Referenzen zurückgibt, wird $wert * innerhalb von generiere_referenz() verändert. */ foreach (generiere_referenz() as &$nummer) { echo (--$nummer).'... '; } ?> ]]> &example.outputs; Generatordelegation per <command>yield from</command> Die Generatordelegation ermöglicht, mittels yield from-Ausdruck Werte von einem anderen Generator, Traversable-Objekt oder Array liefern zu lassen. Der äußere Generator liefert dann alle Werte vom inneren Generator, Objekt oder Array, bis dies nicht mehr gültig ist und die Ausführung im äußeren Generator fortfährt. Falls ein Generator mit yield from verwendet wird, gibt der yield from-Ausdruck auch alle Werte zurück, die vom inneren Generator zurückgegeben werden. Speichern in ein Array (&zb; mit <function>iterator_to_array</function>) yield from setzt nicht die Schlüssel zurück. Es erhält die Schlüssel, die vom Traversable-Objekt oder Array zurückgegeben wurden. Daher können einige Werte den selben Schlüssel mit einem anderen yield oder yield from gemein haben, der, bei der Einfügung in ein Array, vorherige Werte mit diesem Schlüssel überschreibt. Ein üblicher Fall, für den dies relevant ist, ist iterator_to_array, das standardmäßig ein indexiertes Array zurück gibt, was zu möglicherweise unerwarteten Ergebnissen führen kann. iterator_to_array hat einen zweiten Parameter preserve_keys, der auf &false; gesetzt werden kann, um alle Werte zu sammeln, während die Schlüssel, die vom Generator geliefert werden, ignoriert werden. <command>yield from</command> mit <function>iterator_to_array</function> ]]> &example.outputs; int(1) [1]=> int(4) [2]=> int(3) } ]]> Grundlegende Verwendung von <command>yield from</command> ]]> &example.outputs; <command>yield from</command> und Rückgabewerte getReturn(); ?> ]]> &example.outputs; Vergleich von Generatoren mit <classname>Iterator</classname>-Objekten Der Hauptvorteil von Generatoren ist deren Einfachheit. Viel weniger Boilerplate-Code muss geschrieben werden als im Vergleich zur Implementierung einer Iterator-Klasse und der Code ist generell einfacher zu lesen. Zum Beispiel sind die folgende Funktion und Klasse äquivalent: fileHandle = fopen($fileName, 'r')) { throw new RuntimeException('Couldn\'t open file "' . $fileName . '"'); } } public function rewind() { fseek($this->fileHandle, 0); $this->line = fgets($this->fileHandle); $this->i = 0; } public function valid() { return false !== $this->line; } public function current() { return $this->line; } public function key() { return $this->i; } public function next() { if (false !== $this->line) { $this->line = fgets($this->fileHandle); $this->i++; } } public function __destruct() { fclose($this->fileHandle); } } ?> ]]> Diese Flexibilität kommt allerdings nicht ohne Preis: Generatoren sind forward-only-Iteratoren, und können nicht zurückgesetzt werden, wenn sie einmal gestartet wurden. Das heißt außerdem, dass der selbe Generator nicht mehrfach iteriert werden kann: der Generator muss durch einen erneuten Aufruf der Generator-Funktion neu erstellt werden. &reftitle.seealso; Objektiteration