PHP
generatorer
Sök…
Varför använda en generator?
Generatorer är användbara när du behöver generera en stor samling för att senare uppdateras. De är ett enklare alternativ till att skapa en klass som implementerar en Iterator , som ofta är överdödig.
Tänk till exempel på funktionen nedan.
function randomNumbers(int $length)
{
$array = [];
for ($i = 0; $i < $length; $i++) {
$array[] = mt_rand(1, 10);
}
return $array;
}
All denna funktion gör är att generera en matris som är fylld med slumpmässiga nummer. För att använda det kan vi göra randomNumbers(10)
, vilket ger oss en matris med 10 slumpmässiga nummer. Tänk om vi vill generera en miljon slumpmässiga nummer? randomNumbers(1000000)
kommer att göra det för oss, men till en minneskostnad. En miljon heltal lagrade i en grupp använder ungefär 33 megabyte minne.
$startMemory = memory_get_usage();
$randomNumbers = randomNumbers(1000000);
echo memory_get_usage() - $startMemory, ' bytes';
Detta beror på att hela en miljon slumpmässiga nummer genereras och returneras samtidigt, snarare än en åt gången. Generatorer är ett enkelt sätt att lösa problemet.
Omskriva randomNumbers () med hjälp av en generator
Vår randomNumbers()
-funktion kan skrivas om för att använda en generator.
<?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";
}
Med en generator behöver vi inte bygga en hel lista med slumpmässiga nummer för att återvända från funktionen, vilket leder till att mycket mindre minne används.
Läser en stor fil med en generator
Ett vanligt fall för generatorer är att läsa en fil från disken och iterera över dess innehåll. Nedan är en klass som låter dig iterera över en CSV-fil. Minnesanvändningen för detta skript är mycket förutsägbart och kommer inte att variera beroende på storleken på CSV-filen.
<?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.
}
Utbyte nyckelord
Ett yield
liknar ett returrätt, förutom att istället för att stoppa exekveringen av funktionen och returnera, returnerar avkastningen istället ett Generator- objekt och pausar exekveringen av generatorfunktionen.
Här är ett exempel på intervallfunktionen, skriven som en generator:
function gen_one_to_three() {
for ($i = 1; $i <= 3; $i++) {
// Note that $i is preserved between yields.
yield $i;
}
}
Du kan se att den här funktionen returnerar ett Generator- objekt genom att kontrollera utgången från var_dump
:
var_dump(gen_one_to_three())
# Outputs:
class Generator (0) {
}
Utbyte värden
Generator- objektet kan sedan itereras över som en matris.
foreach (gen_one_to_three() as $value) {
echo "$value\n";
}
Exemplet ovan kommer att matas ut:
1
2
3
Att ge värden med nycklar
Förutom att ge värden kan du också ge nyckel- / värdepar.
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";
}
Exemplet ovan kommer att matas ut:
first: 1
second: 2
third: 3
Använd funktionen skicka () - för att skicka värden till en generator
Generatorer är snabbkodade och i många fall ett smalt alternativ till tunga iterator-implementationer. Med den snabba implementeringen kommer lite brist på kontroll när en generator ska sluta generera eller om den skulle generera något annat. Detta kan emellertid uppnås genom att använda funktionen send()
, vilket gör att den begärande funktionen kan skicka parametrar till generatorn efter varje slinga.
//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.
}
}
Detta resulterar i denna utgång: