Nous allons voir une nouveauté de PHP 5.5 l’instruction yield
Cela permet de mettre en place ce qu’on appelle les générateurs.
Un premier exemple
Regardons un exemple ensemble
123456789101112131415
<?phpfunctiongenerateAnimal(){echo"Je suis dans le générateur\n";yield"Panda";echo"Je suis retourné dans le générateur\n";yield"Lama";echo"je suis de retour\n";yield"Alpaga";echo"plus de d'animaux\n";}$generator=generateAnimal();foreach($generatoras$value){echo"j'ai reçu $value\n";}
Voici le résultat
123456
j'ai reçu Panda Je suis retourné dans le générateurj'aireçuLamajesuisderetourj'ai reçu Alpaga plus de d'animaux
D’abord un générateur se comporte comme un iterator. C’est grâce à cela que je peux faire un foreach.
Je vais refaire pas à pas avec des commentaires.
premier passage
1234567891011121314151617
<?phpfunctiongenerateAnimal(){echo"Je suis dans le générateur\n";yield"Panda";// Je retourne ici echo"Je suis retourné dans le générateur\n";yield"Lama";echo"je suis de retour\n";yield"Alpaga";echo"plus de d'animaux\n";}$generator=generateAnimal();echo$generator->current();// "je suis dans le générateur// $value = "Panda"
Itération suivante
En fait le générateur reste en suspens, yield est un pseudo return (enfin c’est comme cela que je l’ai compris)
12345678910111213141516
<?phpfunctiongenerateAnimal(){echo"Je suis dans le générateur\n";yield"Panda";// Je suis reste ici .. je continue echo"Je suis retourné dans le générateur\n";yield"Lama";// je m'arrete à nouveau echo"je suis de retour\n";yield"Alpaga";echo"plus d'animaux\n";}$generator->next()// On récupère la valeur suivanteecho$generator->current();// "je suis retourné dans le générateur// "Lama"
Troisième itération
12345678910111213141516
<?phpfunctiongenerateAnimal(){echo"Je suis dans le générateur\n";yield"Panda";echo"Je suis retourné dans le générateur\n";yield"Lama";// je me suis arrété ici echo"je suis de retour\n";yield"Alpaga";// je retourne .. echo"plus d'animaux\n";}$generator->next()// On récupère la valeur suivanteecho$generator->current();// Je suis de retour// "Alpaga"
Dernière Itération
Nous y sommes presque..
123456789101112131415
<?phpfunctiongenerateAnimal(){echo"Je suis dans le générateur\n";yield"Panda";echo"Je suis retourné dans le générateur\n";yield"Lama";echo"je suis de retour\n";yield"Alpaga";// je me suis arréte iciecho"plus d'animaux\n";//pas de yield je renvoie null..}$generator->next()// On récupère la valeur suivanteecho$generator->current();// Plus d'animaux // il n'y a rien car echo null;
Une fois qu’un générateur a fini, on ne peux le réutiliser
l’énorme avantage est que je n’ai pas besoin de générer un array de 1 Millions de lignes, je génère valeur par valeur. Si la fonction est appelle deux fois je ne génère que deux valeurs. L’occupation en mémoire est faible. Les valeurs sont instanciées paresseusements.
Un exemple encore plus concret.
Pour lire un fichier:
1234567891011
functiongetLinesFromFile($fileName){$fileHandle=fopen($fileName,'r');while(false!==$line=fgets($fileHandle)){yield$line;}fclose($fileHandle);}$lines=getLinesFromFile($fileName);foreach($linesas$line){// do something with $line}
Ce code a plusieurs avantages.
On va chercher la ligne à la demande.
Il y a une couche d’abstraction entre la lecture et le programme principale.
Un petit quizz
Pouvez vous deviner la fonction suivante ?
12345678910111213141516171819
functionmystere(){$last=0;$current=1;yield1;while(true){list($current,$last)=array($current+$last,$current);yield$current;}}$count=0;foreach(mystere()as$value){$count++;echo$value."\n";if($count>10){break;// pas cool la boucle infinie}}
Une mise au point
Les générateurs se comportent comme des itérateurs, mais pour implémenter un iterator il faut implémenter l’interface suivante.
classLineIteratorimplementsIterator{protected$fileHandle;protected$line;protected$i;publicfunction__construct($fileName){if(!$this->fileHandle=fopen($fileName,'r')){thrownewRuntimeException('Impossible d\'ouvrir le fichier : "'.$fileName.'"');}}publicfunctionrewind(){fseek($this->fileHandle,0);$this->line=fgets($this->fileHandle);$this->i=0;}publicfunctionvalid(){returnfalse!==$this->line;}publicfunctioncurrent(){return$this->line;}publicfunctionkey(){return$this->i;}publicfunctionnext(){if(false!==$this->line){$this->line=fgets($this->fileHandle);$this->i++;}}publicfunction__destruct(){fclose($this->fileHandle);}}
L’implémentation en générateur.
1234567891011
functiongetLinesFromFile($fileName){if(!$fileHandle=fopen($fileName,'r')){thrownewRuntimeException('Impossible d\'ouvrir le fichier : "'.$fileName.'"');}while(false!==$line=fgets($fileHandle)){yield$line;}fclose($fileHandle);}
C’est quand même plus simple.
En conclusion.
Cela existe aussi dans les autres langages
On trouve l’instruction yield surtout dans python
1234567
defcountdown(n):whilen>0:yieldnn-=1forxincountdown(10):print'depart dans %s'%x
La référence est ce site , Il existe une video (3 heures !!!)
Cela existe aussi dans ruby, C#, et dans le javascript ES6
C’est un peu plus qu’une nouvelle syntaxe. Cela permet de faire du code asynchrone. Car cela permet une structure de codage que l’on appelle: Les Couroutines. Mais plus d’info dans un prochain post .