Utiliser les itérateurs en php5

Les itérateurs

ouh la ! Un grand sujet de PHP5 !

Introduction

Il faut savoir que les itérateurs, c'est un gros morceaux de la SPL fournit par PHP5 (compilé et activé par défaut), et qui n'est pas si facile que ça à comprendre du premier coup.

Si je fais ce petit tuto, c'est pour que vous compreniez dans son ensemble ce que sont les itérateurs (on poussera plus loin éventuellement plus tard ^^).

D'abord comment ça marche ?

Les itérateurs fonctionnent sur des classes, et seulement des classes ! Les itérateurs sont des interfaces qui fournissent des méthodes publiques pour accéder aux instances de classes.

L'itérateur "standard" nécessite 5 méthodes publiques ( key(), valid(), next(), rewind(), current() ).

Voici ce que cela donne en réel :
Tout d'abord, la classe :

<?php
 class x implements iterator {
  private $output;
  private $query;
  private $key;

  public function __construct($query) {
   $this->query = $query;
  }
 
 public function key() {
  return $this->key;
 }

 public function valid() {
 return isset($this->output);
 }

 public function next() {
 $this->output = mysql_fetch_array($this->query);
 $this->key++;
 }

 public function rewind() {
 mysql_seek(0); // Je crois...
 $this->key = 0;
 }

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

Voyons maintenant comment cela peut fonctionner :

<?php
 $Iterate = new x( mysql_query('SELECT * FROM news') );
 foreach ( $Iterate as $key->$news ) {
   echo $news['id'];
   echo $news['titre'];
   echo $news['contenu']; // etc...
 }
?>

Et voila, c'est aussi simple ! Alors vous allez me demandez pourquoi utiliser les itérateurs alors que je peux utiliser un simple :

while ( $data = mysql_fetch_array(...) ) { 
 //
} 

Pourquoi utiliser les itérateurs ?

Imaginons que vous souhaitez, en plus de développer un système de news, un système de commentaires, de gestions de liens, de téléchargements, de threads pour un forum, de topics, de replys etc....

Déjà, nous partons dans le principe d'une gestion objet.
Cela signifie, dans un premier temps, que mes classes qui vont gérer les modules ne sont pas censé savoir d'où proviennent les données ! C'est la que ça devient intéressant.
Je peux récupérer mes news via SQL, mais aussi via XML ou via fichiers simples... ou RSS bref, le choix est étendu !
Vous vous voyez copier 40 fois le code avec des if() ??? Pas moi.

La où les itérateurs prennent tous leurs sens, c'est qu'on peut y faire passer ce que l'on veut dedans et en faire ressortir également ce que l'on veut.
Un exemple avec un niveau d'abstraction plus élevé :

<?php
 class x implements iterator {
  private $output;
  private $resource;
  private $key;
  private $class_input;
  private $class_output;

  public function __construct($resource, $nom_de_la_classe_où_les_donnees_proviennent, $nom_de_la_classe_où_les_donnees_vont_sortir) {
   $this->resource = $resource;
   $this->class_input = new $nom_de_la_classe_où_les_donnees_proviennent;
   $this->class_output = $nom_de_la_classe_où_les_donnees_vont_sortir;
  }
 
 public function key() {
  return $this->key;
 }

 public function valid() {
 return isset($this->output);
 }

 public function next() {
 $this->output = new $this->class_output( $this->class_input('Next_Element', $resource) ); // C'est mal écrit mais je ne peux pas faire autrement, disons que je dit
// que je veux faire passer la méthode qui va me permettre de passer à l'élément suivant de ma ressource et renvoyer le résultat de cette méthode dans ma classe d'output.
 $this->key++;
 }

 public function rewind() {
 $this->class_input('Rewind_Elements');
 $this->key = 0;
 }

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

Je prend l'exemple d'une classe SQL :

<?php
 class sql {
  // je passe la classe en détail ...
static public function Next_Element($query) {
 return mysql_fetch_array($query);
}
 }
?>

Et maintenant, si je veux récupérer des news :

<?php
 $sql = new mysql;
 $IterateMe = new x ( $sql->query('SELECT * FROM news'), $sql, 'news');
 foreach ( $IterateMe as $key=>$news) {
      // $news est une instance de la classe 'news' comme je l'ai demandé !!
 }

// Si je veux faire pareil avec des liens :
$IterateMe = new x ($sql->query('SELECT * FROM links'), $sql, 'liens');
 foreach ( $IterateMe as $key=>$link) {
  // $link est une instance de la classe 'liens' comme je l'ai aussi demandé :)
 }

?>

Et je peux aussi utiliser XML à la place. Il me suffit de remplacer la requête SQL par une requête Xpath, d'envoyer la classe qui permet d'utiliser la méthode 'Next_Element' pour XML et ça marche aussi bien :)

L'itérateur permet ici de faire du bouclage quelque soit l'entrée et quelque soit la sortie.
On peut l'utiliser avec le principe de réflection pour ajouter un peu de dynamisme et de lisibilité d'écriture...

Conclusion

Mais l'itérateur ne s'arrête pas la. Il existe d'autres formules d'itérations possible ( des dossiers, des itérateurs d'itérateurs ... des agrégations d'itérateurs...), bref tout devient possible !
En tout cas, après avoir lu ce tuto, j'espère ne plus voir quelque chose comme :

<?php
class mysql {
 //
 public function GetAllNews() {
  $array = array();
   while($data = mysql_fetch_array($this->query) ) {
           $array *  = $data;
   }
  return $array;
 }

}

$sql = new mysql;
$allnews = $sql->GetAllNews();
 foreach ( $allnews as $key=>$news ) {
   //
 }
?>

C'est ce que je vois bien trop souvent... parcourir deux fois ses données ... une belle perte de temps :) Maintenant avec les itérateurs, tout devient possible en une seule fois !! :)

Ce document intitulé « Utiliser les itérateurs en php5 » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.