Recherche…


Pourquoi utiliser un générateur?

Les générateurs sont utiles lorsque vous devez générer une collection volumineuse pour effectuer une itération ultérieure. Ils constituent une alternative plus simple à la création d’une classe qui implémente un itérateur , souvent excessif.

Par exemple, considérez la fonction ci-dessous.

function randomNumbers(int $length)
{
    $array = [];
    
    for ($i = 0; $i < $length; $i++) {
        $array[] = mt_rand(1, 10);
    }
    
    return $array;
}

Toute cette fonction génère un tableau rempli de nombres aléatoires. Pour l'utiliser, nous pourrions faire des randomNumbers(10) , ce qui nous donnerait un tableau de 10 nombres aléatoires. Et si on veut générer un million de nombres aléatoires? randomNumbers(1000000) fera pour nous, mais au prix de la mémoire. Un million d'entiers stockés dans un tableau utilise environ 33 Mo de mémoire.

$startMemory = memory_get_usage();

$randomNumbers = randomNumbers(1000000);

echo memory_get_usage() - $startMemory, ' bytes';

Cela est dû au fait qu’un million de nombres aléatoires sont générés et renvoyés en une fois, et non un à la fois. Les générateurs sont un moyen facile de résoudre ce problème.

Réécriture de randomNumbers () à l'aide d'un générateur

Notre fonction randomNumbers() peut être randomNumbers() pour utiliser un générateur.

<?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";
}

En utilisant un générateur, nous n'avons pas besoin de construire une liste complète de nombres aléatoires pour revenir de la fonction, ce qui réduit considérablement la quantité de mémoire utilisée.

Lecture d'un fichier volumineux avec un générateur

Un cas d'utilisation courant pour les générateurs est la lecture d'un fichier à partir du disque et l'itération de son contenu. Vous trouverez ci-dessous une classe qui vous permet de parcourir un fichier CSV. L'utilisation de la mémoire pour ce script est très prévisible et ne varie pas en fonction de la taille du fichier CSV.

<?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.
}

Le rendement

Une déclaration de yield est similaire à une instruction return, sauf qu'au lieu d'arrêter l'exécution de la fonction et de renvoyer, yield renvoie à la place un objet Generator et interrompt l'exécution de la fonction generator.

Voici un exemple de la fonction range, écrite en générateur:

function gen_one_to_three() {
    for ($i = 1; $i <= 3; $i++) {
        // Note that $i is preserved between yields.
        yield $i;
    }
}

Vous pouvez voir que cette fonction renvoie un objet Generator en inspectant la sortie de var_dump :

var_dump(gen_one_to_three())

# Outputs:
class Generator (0) {
}

Valeurs de rendement

L'objet Generator peut ensuite être itéré comme un tableau.

foreach (gen_one_to_three() as $value) {
    echo "$value\n";
}

L'exemple ci-dessus affichera:

1
2
3

Valeurs de rendement avec les clés

Outre les valeurs de rendement, vous pouvez également générer des paires clé / valeur.

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";
}

L'exemple ci-dessus affichera:

first: 1
second: 2
third: 3

Utilisation de la fonction send () pour transmettre des valeurs à un générateur

Les générateurs sont codés rapidement et, dans de nombreux cas, constituent une alternative de taille aux implémentations lourdes d’itérateurs. Avec la mise en œuvre rapide vient un petit manque de contrôle lorsqu'un générateur doit cesser de générer ou s'il doit générer quelque chose d'autre. Cependant, cela peut être réalisé avec l'utilisation de la fonction send() , permettant à la fonction demandeuse d'envoyer des paramètres au générateur après chaque boucle.

//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.
    }
}

Résultat dans cette sortie:

entrer la description de l'image ici



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow