PHP
generatoren
Zoeken…
Waarom een generator gebruiken?
Generatoren zijn handig wanneer u een grote verzameling moet genereren om later opnieuw te kunnen doorlopen. Ze zijn een eenvoudiger alternatief voor het maken van een klasse die een Iterator implementeert, die vaak overkill is.
Overweeg bijvoorbeeld de onderstaande functie.
function randomNumbers(int $length)
{
$array = [];
for ($i = 0; $i < $length; $i++) {
$array[] = mt_rand(1, 10);
}
return $array;
}
Het enige dat deze functie doet, is een array genereren die is gevuld met willekeurige getallen. Om het te gebruiken, kunnen we randomNumbers(10)
, wat ons een array van 10 willekeurige getallen geeft. Wat als we een miljoen willekeurige getallen willen genereren? randomNumbers(1000000)
zal dat voor ons doen, maar dit kost geheugenkosten. Eén miljoen gehele getallen die in een array zijn opgeslagen, gebruiken ongeveer 33 megabytes geheugen.
$startMemory = memory_get_usage();
$randomNumbers = randomNumbers(1000000);
echo memory_get_usage() - $startMemory, ' bytes';
Dit komt doordat de hele miljoen willekeurige getallen in één keer worden gegenereerd en geretourneerd, in plaats van één voor één. Generatoren zijn een gemakkelijke manier om dit probleem op te lossen.
Willekeurige nummers () opnieuw schrijven met een generator
Onze functie randomNumbers()
kan opnieuw worden geschreven om een generator te gebruiken.
<?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";
}
Met behulp van een generator hoeven we niet een hele lijst met willekeurige getallen samen te stellen om terug te keren van de functie, waardoor er veel minder geheugen wordt gebruikt.
Een groot bestand lezen met een generator
Een veelgebruikt geval voor generatoren is het lezen van een bestand van schijf en itereren van de inhoud ervan. Hieronder staat een klasse waarmee u een CSV-bestand kunt herhalen. Het geheugengebruik voor dit script is zeer voorspelbaar en fluctueert niet, afhankelijk van de grootte van het CSV-bestand.
<?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.
}
Het zoekwoord voor de opbrengst
Een yield
is vergelijkbaar met een terugkeerinstructie, behalve dat in plaats van de uitvoering van de functie te stoppen en terug te keren, opbrengst in plaats daarvan een Generator- object retourneert en de uitvoering van de generatorfunctie pauzeert.
Hier is een voorbeeld van de bereikfunctie, geschreven als een generator:
function gen_one_to_three() {
for ($i = 1; $i <= 3; $i++) {
// Note that $i is preserved between yields.
yield $i;
}
}
Je kunt zien dat deze functie een Generator- object retourneert door de uitvoer van var_dump
:
var_dump(gen_one_to_three())
# Outputs:
class Generator (0) {
}
Waarden opleveren
Het Generator- object kan dan als een array worden herhaald.
foreach (gen_one_to_three() as $value) {
echo "$value\n";
}
Het bovenstaande voorbeeld zal uitvoeren:
1
2
3
Waarden opleveren met sleutels
Naast het opleveren van waarden, kunt u ook sleutel / waarde-paren opleveren.
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";
}
Het bovenstaande voorbeeld zal uitvoeren:
first: 1
second: 2
third: 3
Gebruik van de functie send () - om waarden door te geven aan een generator
Generatoren zijn snel gecodeerd en in veel gevallen een slank alternatief voor zware iterator-implementaties. Met de snelle implementatie komt een beetje gebrek aan controle wanneer een generator moet stoppen met genereren of als het iets anders moet genereren. Dit kan echter worden bereikt met het gebruik van de functie send()
, waardoor de verzoekende functie na elke lus parameters naar de generator kan verzenden.
//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.
}
}
Met als resultaat deze uitvoer: