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

Les rubriques (actu, forums, tutos) de Développez
Tags
Réseaux sociaux


 Discussion forum

Sur le même sujet
Le , par tarikbenmerar, Chroniqueur Actualités
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 :
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 :
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 :
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 ?


Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :


 Poster une réponse

Avatar de spidermario spidermario
http://www.developpez.com
Membre chevronné
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 :
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.
Avatar de Lupus Michaelis Lupus Michaelis
http://www.developpez.com
Membre du Club
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.
Avatar de _skip _skip
http://www.developpez.com
Expert Confirmé Sénior
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.
Avatar de Grimly_old Grimly_old
http://www.developpez.com
Membre éclairé
le 05/09/2012 10:29
Ce mot clé est un raccourci pour cette structure :

Code :
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.
Avatar de transgohan transgohan
http://www.developpez.com
Expert Confirmé Sénior
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 :
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.
Avatar de laerne laerne
http://www.developpez.com
Membre éprouvé
le 05/09/2012 11:23
En bref, c'est du lazy-evaluation ? Mais limité pour un itérateur ?
Avatar de yohannc yohannc
http://www.developpez.com
Membre actif
le 05/09/2012 11:24
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.

Je ne suis pas expert sur le sujet, mais cela repose plutôt sur un système de pointeurs que d'exécution parallèles, et cela semble être l'implémentation classique du mot clé Yield.
Avatar de camus3 camus3
http://www.developpez.com
Membre Expert
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.
Avatar de _skip _skip
http://www.developpez.com
Expert Confirmé Sénior
le 05/09/2012 13:00
Citation Envoyé par yohannc  Voir le message
Je ne suis pas expert sur le sujet, mais cela repose plutôt sur un système de pointeurs que d'exécution parallèles, et cela semble être l'implémentation classique du mot clé Yield.

Oui, il faut imaginer que la boucle consommateur exécute elle-même la boucle fournisseur de façon transparente à chaque tour de boucle dans son propre thread. Si on le déroule on obtient surement un truc proche de ce qu'a posté Grimly.
Avatar de ePoX ePoX
http://www.developpez.com
Membre Expert
le 05/09/2012 14:43
Citation Envoyé par _skip  Voir le message
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.


C'est du php. Le fichier toujours est présent, disponible en lecture, et le disque dur n'à jamais de défaillances.
Sinon, c'est qu'il y à un problème qui mérite un déboggage manuel.

Sans troll aucun, php est un langage et une communauté optimiste.

mes 2 cents

PS: Je vous vois venir les aficionados de l'exception ect. Ce n'est pas la peine de me rétorquer on peut faire ceci-cela, car oui vous aurez probablement raison, mais non ce n'est pas la philosophie de ce langage simple, syntaxiquement épuré et simplement fonctionnel.
Offres d'emploi IT
Cto python / django
CDI [Autre]
Homeloc - Ile de France - Paris (75000)
Parue le 05/08/2014
Développeur application mobile et réseaux sociaux
Stage
LITOO - Ile de France - Paris (75000)
Parue le 30/08/2014
Dev ops multimédia
CDI
Mobiskill - Ile de France - Paris (75000)
Parue le 20/08/2014

Voir plus d'offres Voir la carte des offres IT
 
 
 
 
Partenaires

PlanetHoster
Ikoula