Dotaz na yield (PHP)

Dotaz na yield (PHP)
« kdy: 19. 10. 2019, 23:12:56 »
Přečetl jsem si článek nazvaný "How to Read Big Files with PHP (Without Killing Your Server)", kde se píše, že yield se dá použit na streamování, aby funkce nežrali tolik paměti.

Mám takovýto kód:
Kód: [Vybrat]
$emails = file("adresy.txt");
foreach ($emails as $i => $em):
   $em = emailReplace(strtolower(trim($em)));
   $line = createLineWithTabs($i,$em,$psw1,$users_encoded_id_str_length);
   $line = getLineWithTabs($line, $offset);
   $indexes[$line] = $offset;
endforeach;
ksort($indexes);

Zajímá mě, jestli by se z toho kódu mezi file() a ksort() dala udělat funkce která bude yieldovat výstup do toho $indexes.

V ukázkovém ködu jsem totiž našel něco podobného:
Kód: [Vybrat]
$lines[] = trim(fgets($handle));bylo nahrazeno za
Kód: [Vybrat]
yield trim(fgets($handle));
Takže bych chtěl udělat něco takového:
Kód: [Vybrat]
function yieldEmails(&$emails){...}
indexes = yieldEmails($emails);
A výsledkem by bylo to, že indexes bude obsahovat ty emaily namísto klíčů, a offsety namísto hodnot.

Dá se to udělat? A jestli jo, jak na to?

Pozn. Tato funkce je zatím jen dočasná a používám ji na provizorní sestavení souboru s uživatelskými emaily a hesly. Prostě to připravuje data abych mohl odladit zbytek programu, ale chtěl bych se to na tom naučit jestli to jde.


tecka

  • ***
  • 138
    • Zobrazit profil
    • E-mail
Re:Dotaz na yield (PHP)
« Odpověď #1 kdy: 20. 10. 2019, 01:38:10 »
Ten článek popisuje, že není potřeba držet všechny řádky v paměti, ale lze je po jednom yieldovat. Ten zpracovávací foreach a generátor se pak v běhu střídají. Generátor postupně generuje hodnoty tak, jak si o ně foreach "říká" a ve "vzduchu" je vždycky jen jedna hodnota (řádek).

Je to tvůj případ?

Re:Dotaz na yield (PHP)
« Odpověď #2 kdy: 20. 10. 2019, 09:34:36 »
Měl jsem spíš ten příklad s while.

Není to můj případ. To pole indexes je ale jen dočasné pole, které slouží na uspořádání prvků podle emailů. Přitom ale informace email, offset musí držet společně v jednom uskupení. Mohl bych tedy změnit strukturu na

pole [ 0 => array(email, offset), 1 => array(email, offset), atd ].

Ale pro tento případ nevím jak to dále udělat (poté co dokončím generování), abych to pole uspořádal. Potřebuju na to napsat vlastní funkci usort, nebo mám na to použít array_multisort? Co se na takovou situaci hodí?
« Poslední změna: 20. 10. 2019, 09:41:40 od exkalibr »

Re:Dotaz na yield (PHP)
« Odpověď #3 kdy: 20. 10. 2019, 09:47:23 »
Koukám ale že jsem dostal chybu:
Parse error: syntax error, unexpected 'array'

copak to nemůže fungovat?
yield array( $line, $offset);

Re:Dotaz na yield (PHP)
« Odpověď #4 kdy: 20. 10. 2019, 10:22:15 »
Zajímá mě, jestli by se z toho kódu mezi file() a ksort() dala udělat funkce která bude yieldovat výstup do toho $indexes.

Vložit yield mezi file() a ksort() mi v tom kontextu nedává smysl, podle mne nic neušetříš.
Na začátku totiž načítáš najednou celý obsah souboru do paměti.
A právě aby nemusel být celý soubor v paměti najednou, můžeš přepsat kód tak že bude číst data po jednotlivých řádkách (např tou funkcí fgets( )) a zpracovávávat postupně. Během zpracovávání souboru pak bude muset být v paměti jen právě jedna zpracovávaná řádka. (mluvíme tady o vstupních datech $emails, nikoliv o datech které ukládáš do indexes)

Namísto načtení kompletního obsahu souboru pomocí $emails = file("adresy.txt"); bys tedy mohl použít "generátor" $emails = getLines("adresy.txt");
To pak funguje tak že v tom foreach se vždy provede kousek toho "generátoru" až dokud nevrátí hodnotu pomocí yield.
Pak se provede kód v tom foreach a pak zase ze provádí kód v tom generártoru,od místa kde skončil, až do dalšího yield, a tak pořád dokola až dokud generátor neskončí.
Nahrazení tohoto prvního řádku (a přidání generátoru dat jednotlivých řádek) je jediná věc co bys měl v kontextu tvého odkazovaného článku udělat.

Výhoda je že si můžeš do toho "generátoru" dát další logiku, npř filtrovat nevhodné řádky nebo třeba kompletně parsovat a vracet už zpracované řádky nebo třeba kompletní objekty atp.
Případně se dají generátory vrstvit, třeba jako filtry atd.


P.S. nezapomeň také ošetřit uzavření souboru v případě vyjímky (což je dobrý nápad v každém případě)
např zde: https://www.php.net/manual/en/language.generators.overview.php
Kód: [Vybrat]
function getLines($file) {
    $f = fopen($file, 'r');
    try {
        while ($line = fgets($f)) {
            yield $line;
        }
    } finally {
        fclose($f);
    }
}


Re:Dotaz na yield (PHP)
« Odpověď #5 kdy: 20. 10. 2019, 10:33:32 »
No ono to jde na číst i pomocí file_get_contents, mě spíš asi šlo o to si to vyzkoušet a otestovat jak dlouho ta funkce bude trvat. O kolik se prodlouží její čas a jestl se prodlouží... Jak říkám je to vlastně jen takový test, takže ta část, která mě zajímá je právě ta co jsem zmínil.

Načítání souboru jsem tu opravdu řešit nechtěl. Teď přemýšlím jak uspořádat to pole s elementy typu array( emails, offset)
« Poslední změna: 20. 10. 2019, 10:35:04 od exkalibr »

Re:Dotaz na yield (PHP)
« Odpověď #6 kdy: 20. 10. 2019, 10:54:02 »
Yak to teda správně napsat?

Kód: [Vybrat]
function yieldEmails($emails){
$start_time = microtime(true);
foreach ($emails as $i => $em):
         $em = emailReplace(strtolower(trim($em)));
         $line = createLineWithTabs($i,$em,$psw1,$users_encoded_id_str_length);
         $line = getLineWithTabs($line, $offset);
         yield array( $line, $offset);
endforeach;
}

Kód: [Vybrat]
function yieldEmails($emails){
$start_time = microtime(true);
foreach ($emails as $i => $em):
         $em = emailReplace(strtolower(trim($em)));
         // complete_line is used to sort
         $line = createLineWithTabs($i,$em,$psw1,$users_encoded_id_str_length);
         $line = getLineWithTabs($line, $offset);
         $line = array( $line, $offset);
         yield $line;
endforeach;
}

To dává tu stejnou chybu, že
Parse error: syntax error, unexpected '$line' (T_VARIABLE)
« Poslední změna: 20. 10. 2019, 10:57:20 od exkalibr »

RDa

  • *****
  • 2 465
    • Zobrazit profil
    • E-mail
Re:Dotaz na yield (PHP)
« Odpověď #7 kdy: 20. 10. 2019, 11:47:41 »
To dává tu stejnou chybu, že
Parse error: syntax error, unexpected '$line' (T_VARIABLE)

Zkontroluj verzi PHP.. me tento kod pres php -l projde.

Re:Dotaz na yield (PHP)
« Odpověď #8 kdy: 20. 10. 2019, 11:50:02 »
PHP 5.4 a teď jsem zjistil že generátory fungují až od verze 5.5