Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

PHP introduit les générateurs
Par un mécanisme similaire à celui de Python avec le mot-clé yield

Le , par tarikbenmerar

0PARTAGES

7  0 
Les générateurs sont un moyen simple et puissant de créer des itérateurs dans des langages tels que Python. Maintenant, c'est PHP qui fait le pas et s'approprie ce concept.

Pour comprendre l'utilité et la puissance de ce dernier, on revoit l'exemple typique de lecture d'un fichier en entier :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function getLinesFromFile($fileName) {
    if (!$fileHandle = fopen($fileName, 'r')) {
        return;
    }

    $lines = [];
    while (false !== $line = fgets($fileHandle)) {
        $lines[] = $line;
    }

    fclose($fileHandle);

    return $lines;
}

$lines = getLinesFromFile($fileName);
foreach ($lines as $line) {
    // do something with $line
}
Le point faible de ce code est le fait qu'il copie tout le fichier dans un grand tableau. Ainsi, plus le fichier est grand, plus le besoin en mémoire s'accroît, avec un risque imminent d'atteindre les limites. Il faut toujours se rappeler qu'un script PHP doit respecter une limite de mémoire spécifiée par l'administrateur.

On peut certainement éviter ce comportement et récupérer les données ligne par ligne, en utilisant les itérateurs qui sont parfaits pour ce cas d'utilisation. Malheureusement, en PHP il n'existait jusque-là aucune manière simple d'implémenter les itérateurs. Pour y arriver, on est amené à créer une classe complexe implémentant une interface Iterator comme suit :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class LineIterator implements Iterator {
    protected $fileHandle;
    protected $line;
    protected $i;

    public function __construct($fileName) {
        if (!$this->fileHandle = fopen($fileName, 'r')) {
            throw new RuntimeException('Couldn\'t open file "' . $fileName . '"');
        }
    }

    public function rewind() {
        fseek($this->fileHandle, 0);
        $this->line = fgets($this->fileHandle);
        $this->i = 0;
    }

    public function valid() {
        return false !== $this->line;
    }

    public function current() {
        return $this->line;
    }

    public function key() {
        return $this->i;
    }

    public function next() {
        if (false !== $this->line) {
            $this->line = fgets($this->fileHandle);
            $this->i++;
        }
    }
    public function __destruct() {
        fclose($this->fileHandle);
    }
}

$lines = new LineIterator($fileName);
foreach ($lines as $line) {
    // do something with $line
}
Pour un cas aussi simple que notre exemple, le code est déjà complexe. Avec les générateurs, on peut considérablement réduire le nombre de lignes de code de manière directe à partir de la première version du code :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function getLinesFromFile($fileName) {
    if (!$fileHandle = fopen($fileName, 'r')) {
        return;
    }

    while (false !== $line = fgets($fileHandle)) {
        //c'est ici la différence
        yield $line;
    }

    fclose($fileHandle);
}

$lines = getLinesFromFile($fileName);
foreach ($lines as $line) {
    // do something with $line
}
La toute petite différence réside dans l'utilisation du mot clé yield, qui génère une nouvelle donnée dans l'itération.

En effet, l'instruction $lines = getLinesFromFile($fileName) ne renvoie aucune donnée, c'est simplement un générateur qui implémente l'itérateur qui vient d'être créé.

Après, pendant l'exécution de la boucle foreach ($lines as $line), chaque itération génère les données renvoyées par yield, qui seront stockées dans $line. En fait, cette génération implémente un objet Iterator et des appels à Iterator::next() seront effectués. L'exécution s'arrête jusqu'à la rencontre du prochain yield, qui renvoie la prochaine donnée, et ainsi de suite...

Quelques jours après l'introduction du mot clé Finally, se succèdent donc pour PHP les bonnes nouvelles. Ou les emprunts d’autres langages, diront certains.

Source : détails du mot clé yield dans le site de PHP

Et vous ?

Quelle impression vous laisse cette annonce ?
Pouvez-vous trouver d'autres cas d'utilisation intéressants ?
Quelle autre approche de simplification des itérateurs auriez-vous préférée pour PHP ?

Une erreur dans cette actualité ? Signalez-le nous !

Avatar de transgohan
Expert éminent https://www.developpez.com
Le 05/09/2012 à 10:38
Je trouve qu'avec l'exemple donnée on perd en lisibilité et compréhension.
Aucun retour de fonction mais il y en a bel et bien un de par l'implication de yield.
Ils viennent de créer un second mot clé de retour de fonction mais que je ne trouve pas très pratique.

J'ai parcouru la rfc et j'ai trouvé d'autres exemples qui me chagrinent comme celui là :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function echoLogger() {
    while (true) {
        echo 'Log: ' . yield . "\n";
    }
}
 
function fileLogger($fileName) {
    $fileHandle = fopen($fileName, 'a');
    while (true) {
        fwrite($fileHandle, yield . "\n");
    }
}
 
$logger = echoLogger();
// or
$logger = fileLogger(__DIR__ . '/log');
 
$logger->send('Foo');
$logger->send('Bar');
En gros on balance des objets en exécution parallèle avec ce yield et c'est loin d'être un comportement/code qu'on a l'habitude d'utiliser avec ce langage.

Voilà pour mes deux sous de réflexions.
4  0 
Avatar de spidermario
Membre éprouvé https://www.developpez.com
Le 05/09/2012 à 0:57
Citation Envoyé par tarikbenmerar  Voir le message
Quelle autre approche de simplification des itérateurs auriez-vous préférée pour PHP ?

En fait, pour l’exemple donné, une autre était déjà possible :
Code php : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function forEachLineFromFile($fileName, $f) { 
    if (!$fileHandle = fopen($fileName, 'r')) { 
        return; 
    } 
  
    while (false !== $line = fgets($fileHandle)) { 
        $f($line); 
    } 
  
    fclose($fileHandle); 
} 
  
forEachLineFromFile($filename, function($line) { 
    // do something with $line 
});

C’est proche de l’approche employée, par exemple, par Scheme et sa fonction call-with-input-file.
3  0 
Avatar de camus3
Membre éprouvé https://www.developpez.com
Le 05/09/2012 à 12:35
C'est bien mais on aimerai aussi que de vieux "bugs" soient corrigés , hein

http://phpsadness.com/

mais toute nouveauté est bonne à prendre.
1  0 
Avatar de pcdwarf
Membre éclairé https://www.developpez.com
Le 05/09/2012 à 19:21
Je m'étonne du code impératif donné en exemple avec pour argument principal que ça fait charger tout le fichier en ram et que c'est moche.
En effet Il est inutile et même néfaste de charger tout le fichier en ram si on peut le traiter ligne à ligne.

Sauf qu'il n'y a pas besoin d'usine à gaz pour traiter un fichier en une seule passe.

L'exemple deviens

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
function dosomethingwhithFile($fileName, $linefunction) {
    if (!$fileHandle = fopen($fileName, 'r')) {
        return false;
    }
 
    while (false !== $line = fgets($fileHandle)) {
        $linefunction($line);
    }
 
    fclose($fileHandle);
    return true;
}
La mémoire libérée sera bien mieux utilisée par le système d'exploitation qui est sensé déjà gérer les problématiques de cache/readahead donc ça ne devrait pas être moins performant qu'en lisant tout le fichier d'un coup.

Au cas ou ça ne serait pas clair, je ne critique pas le nouveau "yield" mais simplement que le problème soulevé dans l'exemple et prétendument résolu par ce nouvel élément relève largement plus d'une mauvaise approche que d'une limitation du langage.
1  0 
Avatar de elderion
Membre régulier https://www.developpez.com
Le 06/09/2012 à 11:17
Par rapport a la syntaxe et sa lisibilité :
si je fais une analogie avec un autre langage qui utilise yield :
C# utilise bien "yield return" et pas juste "yield".
J'aurai trouvé plus commode que PHP s'inspire de la syntaxe C# :
de considérer yield comme un complément du return et pas comme un remplacant.
1  0 
Avatar de elderion
Membre régulier https://www.developpez.com
Le 06/09/2012 à 15:42
(ceci reste mon strict point de vue)

j'ai fait quelques pas vers Python et meme Ruby :
sans meme parler de puissance et tout ca
rien que la syntaxe du langage me rebute, ca ressemble a du "vieux code".
Avec des "def", des __INIT__, puis l'encapsulation optionnelle.
C'est pas pour troller, juste expliquer pourquoi je suis réfractaire a Python.

C'est sans doute une question d'affinité, car j'aime la syntaxe Java ou PHP avec des accolades et des points virgule, etc...
Sans ca j'aurais surement adopté Python ou Ruby.
2  1 
Avatar de CyberDenix
Membre à l'essai https://www.developpez.com
Le 17/09/2012 à 22:46
Je vais faire mon captain obvious, mais...

Code php : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
  
while (($line = fgets($fileHandle)) !== false) { 
  
  // Il suffit de traiter la ligne directement ici 
  // -> Consommation mémoire quasi constante 
  // -> Pas besoin d'un yield avec post processing super lent 
  
  doSomething($line); 
  
}

Si c'est juste pour traiter le fichier ligne par ligne, les itérateurs ou le yield me semblent hors de propos.

Mais je loupe peut-être le sens caché de cette formidable avancée technologique ?



Edit : Grilled by spidermario et pcdwarf. J'me sens moins seul !
1  0 
Avatar de Lupus Michaelis
Membre du Club https://www.developpez.com
Le 05/09/2012 à 7:49
J'avais été enchanté de découvrir les générateurs avec Python. Mais c'est avec de la pratique qu'on peut vraiment juger de la pertinence d'une telle fonctionnalité. Donc je vais attendre de pester contre les bogues (et trous de sécurité) qu'aura introduit ce nouveau gadget.
0  0 
Avatar de _skip
Expert éminent https://www.developpez.com
Le 05/09/2012 à 8:32
Ce n'est pas mal à voir comme ça.

Mais déléguer un code de lecture de fichiers à une autre méthode, ça impliquerait presque une gestion d'erreur par exception. Si fgets retourne false, ça ne veut pas dire que la fin du fichier a été atteinte, ça peut être une erreur IO.
0  0 
Avatar de Grimly_old
Membre averti https://www.developpez.com
Le 05/09/2012 à 10:29
Ce mot clé est un raccourci pour cette structure :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Resultat extends Exception {
    var $resultat;
    public function __constructor($resultat) {
        $this->resultat = $resultat;
    }
    
    public function getResultat() {
        return $this->resultat;
    }
}

function monIterateur() {
     $resultat = calculs();
     throw new Resultat($resultat);
}

function utilisation() {
     do {
         try {
               monIterateur();
         } catch (Resultat $r) {
               $line = $r.getResultat();
               $continue = $line === FALSE; //On suppose que nos calculs nous retournent FALSE à la fin de lecture.
               if ($continue !== FALSE) {
                    doStuff($line);
               }
         }
     } while ($continue);
     
}
C'est un mécanisme identique qui se limite à un seul niveau de profondeur alors que dans mon exemple j'aurais pu utiliser autant d'intermédiaire que je souhaite entre "monIterateur" et "utilisation"

Je trouve donc ce mot clé comme un sucre syntaxique inutile.
0  0