PHP
Generatoren
Suche…
Warum einen Generator verwenden?
Generatoren sind nützlich, wenn Sie eine große Sammlung generieren müssen, um sie später zu wiederholen. Sie sind eine einfachere Alternative zum Erstellen einer Klasse, die einen Iterator implementiert, der oft übertrieben ist.
Betrachten Sie beispielsweise die unten stehende Funktion.
function randomNumbers(int $length)
{
$array = [];
for ($i = 0; $i < $length; $i++) {
$array[] = mt_rand(1, 10);
}
return $array;
}
Diese Funktion erzeugt lediglich ein Array, das mit Zufallszahlen gefüllt ist. Um es zu benutzen, könnten wir randomNumbers(10)
, die uns ein Array von 10 Zufallszahlen geben. Was ist, wenn wir eine Million Zufallszahlen generieren möchten? randomNumbers(1000000)
das für uns, allerdings mit Speicherplatz. Eine Million Ganzzahlen, die in einem Array gespeichert sind, benötigen ungefähr 33 MB Speicher.
$startMemory = memory_get_usage();
$randomNumbers = randomNumbers(1000000);
echo memory_get_usage() - $startMemory, ' bytes';
Dies ist darauf zurückzuführen, dass die gesamte eine Million Zufallszahlen nicht einzeln, sondern gleichzeitig generiert und zurückgegeben werden. Generatoren sind eine einfache Möglichkeit, dieses Problem zu lösen.
RandomNumbers () mit einem Generator neu schreiben
Unsere randomNumbers()
Funktion kann zur Verwendung eines Generators neu geschrieben werden.
<?php
function randomNumbers(int $length)
{
for ($i = 0; $i < $length; $i++) {
// yield tells the PHP interpreter that this value
// should be the one used in the current iteration.
yield mt_rand(1, 10);
}
}
foreach (randomNumbers(10) as $number) {
echo "$number\n";
}
Bei Verwendung eines Generators müssen wir nicht eine ganze Liste von Zufallszahlen erstellen, um von der Funktion zurückzukehren.
Eine große Datei mit einem Generator lesen
Ein häufiger Anwendungsfall für Generatoren ist das Lesen einer Datei von der Festplatte und das Durchlaufen ihres Inhalts. Nachfolgend finden Sie eine Klasse, mit der Sie über eine CSV-Datei iterieren können. Die Speicherbelegung für dieses Skript ist sehr vorhersehbar und hängt von der Größe der CSV-Datei ab.
<?php
class CsvReader
{
protected $file;
public function __construct($filePath) {
$this->file = fopen($filePath, 'r');
}
public function rows()
{
while (!feof($this->file)) {
$row = fgetcsv($this->file, 4096);
yield $row;
}
return;
}
}
$csv = new CsvReader('/path/to/huge/csv/file.csv');
foreach ($csv->rows() as $row) {
// Do something with the CSV row.
}
Das Yield-Keyword
Eine yield
Anweisung ähnelt einer Return-Anweisung, mit dem Unterschied, dass anstelle der Ausführung der Funktion und der Rückgabe stattdessen ein Generator- Objekt ausgegeben und die Ausführung der Generatorfunktion angehalten wird.
Hier ist ein Beispiel für die Range-Funktion, geschrieben als Generator:
function gen_one_to_three() {
for ($i = 1; $i <= 3; $i++) {
// Note that $i is preserved between yields.
yield $i;
}
}
Sie können sehen, dass diese Funktion ein Generator- Objekt zurückgibt, indem Sie die Ausgabe von var_dump
:
var_dump(gen_one_to_three())
# Outputs:
class Generator (0) {
}
Ertragswerte
Das Generator- Objekt kann dann wie ein Array durchlaufen werden.
foreach (gen_one_to_three() as $value) {
echo "$value\n";
}
Das obige Beispiel gibt Folgendes aus:
1
2
3
Ertragswerte mit Schlüsseln
Neben dem Ermitteln von Werten können Sie auch Schlüssel-Wert-Paare liefern.
function gen_one_to_three() {
$keys = ["first", "second", "third"];
for ($i = 1; $i <= 3; $i++) {
// Note that $i is preserved between yields.
yield $keys[$i - 1] => $i;
}
}
foreach (gen_one_to_three() as $key => $value) {
echo "$key: $value\n";
}
Das obige Beispiel gibt Folgendes aus:
first: 1
second: 2
third: 3
Verwenden Sie die send () - Funktion, um Werte an einen Generator zu übergeben
Generatoren sind schnell codiert und in vielen Fällen eine schlanke Alternative zu schweren Iterator-Implementierungen. Mit der schnellen Implementierung geht ein wenig die Kontrolle verloren, wenn ein Generator aufhört zu generieren oder wenn er etwas anderes generiert. Dies kann jedoch mit der Funktion send()
werden, sodass die anfordernde Funktion nach jeder Schleife Parameter an den Generator sendet.
//Imagining accessing a large amount of data from a server, here is the generator for this:
function generateDataFromServerDemo()
{
$indexCurrentRun = 0; //In this example in place of data from the server, I just send feedback everytime a loop ran through.
$timeout = false;
while (!$timeout)
{
$timeout = yield $indexCurrentRun; // Values are passed to caller. The next time the generator is called, it will start at this statement. If send() is used, $timeout will take this value.
$indexCurrentRun++;
}
yield 'X of bytes are missing. </br>';
}
// Start using the generator
$generatorDataFromServer = generateDataFromServerDemo ();
foreach($generatorDataFromServer as $numberOfRuns)
{
if ($numberOfRuns < 10)
{
echo $numberOfRuns . "</br>";
}
else
{
$generatorDataFromServer->send(true); //sending data to the generator
echo $generatorDataFromServer->current(); //accessing the latest element (hinting how many bytes are still missing.
}
}
Ergebnis in dieser Ausgabe: