Les iterateurs en PHP, Comment ca marche!

Capture-d’écran-2013-01-21-à-08.08.36
Cet article est une traduction de la vidéo “Iterators” de la série “Programming With Anthnoy” de Anthony Ferrara. Le lien de la vidéo est en bas de l’article.

En Général, un itérateur n’est rien d’autre qu’un générateur de séquence.

A la base, une séquence est une liste ordonnée de valeurs. Ces valeurs peuvent être répétées dans la liste plusieurs fois, et il n’est pas nécessaire qu’elle soient ordonnées.

Le type le plus basique de séquence en programmation est le tableau. Les éléments d’un tableau peuvent avoir de n’importe quelle valeur, et peuvent etre positionnees arbitrairement. Toutefois, une fois ils sont placées, le tableau a un ordre défini.

Si on parcoure ce tableau, chaque boucle aura l’élément prochain dans la séquence, jusqu’à ce que l’on arrive à la fin de ces éléments.

    foreach ($array as $key => $value) {
        echo $value;
    }

Si nous aurions parcouru ce tableau manuellement, nous aurions écrit une boucle for :

    for ($i = 0; $i < count($array); $i++) {
        echo $array[$i];
    }

A la première itération, la valeur de $i est 0,
On teste ensuite si $i est inferieure à la taille du tableau.
On execute par la suite le corps de la boucle.
Finalement, on incremente $i, et on retourne à l’itération 2.

Sans le réaliser, chaque fois que vous écrivez une boucle for comme ceci, vous construisez un itérateur.

Un itérateur n’est rien que l’abstraction de la boucle for.
Nous avons une méthode “rewind()”, qui est la même chose qu’initiaiser $i a 0
Nous avons une méthode “valid()”, qui joue le même rôle que le test si $i est inférieure à la taille du tableau.
Nous avons une méthode “next()” qui est la même chose qu’incrèmenter $i.
Et finalement, nous avons deux fonctions qui sont utiles dans le corps de la boucle :
la méthode “key()”, qui joue le role de recuperer la valeur de $i.
et la méthode “current()” qui recupere la valeur de l’élément a l’index $i.

Donc, si on réécrivait notre boucle avec les itérateurs, ca ressemblerait à quelque chose du genre :

    for ($it->rewind(); $it->valid(); $it->next()) {
        $current = $it->current();
        echo $current;
    }

En réalité, c’est tout ce que la boucle “foreach” fait à l’interne! À l’exception que foreach expose $key et $value automatiquement pour nous.
Donc, notre code original de foreach devient :

    foreach ($it as $key => $value) {
        echo $value;
    }

Notez bien qu’il est presque identique que le premier foreach!
Lorsqu’on utilise foreach, les tableaux et les itérateurs sont interchangeables.

Construisons donc notre premier vrai itérateur. Construisons notre boucle for comme un itérateur :

    class ForLoopIterator implements Iterator {
        protected $array;
        protected $i = 0;
        public function __construct(array $array) {
            $this->array = $array;
        }
        public function rewind() {
            $this->i = 0;
        }
        public function valid() {
            return isset($this->array[$this->i]);
        }
        public function next() {
            $this->i++;
        }
        public function key() {
            return $this->i;
        }
        public function current() {
            return $this->array[$this->i];
        }
    }

Premièrement, Notre classe doit implémenter l’interface Iterator. Cette interface définit les 5 méthodes que nous avons besoin. Mais aussi, elle offre une logique (implémentée dans le langage) qui nous permet d’utiliser l’itérateur dans les construits foreach.

A part ça, cette classe ressemblerait à une facon plus expressive d’une boucle for.

Il est important aussi de noter qu’en pratique, nous n’allons pas écrire cette classe. Nous allons plutôt utiliser la classe core ArrayIterator qui fait essentiellement la même chose.

Donc, maintenant que nous avons notre concept d’itérateur basique, nous pouvons voir comment l’utiliser.

L’un des bénéfices d’utiliser un itérateur est qu’on peut optimiser l’utilisation de memoire en generant uniquement les valeurs dont on a besoin, lorsqu’elles sont necessaires.

Par exemple, essayons de generer une séquence Fibonacci.

    class Fib implements Iterator {
        protected $a = 0;
        protected $b = 1;
        protected $i = 0;
        public function rewind() {
            $this->a = 0;
            $this->b = 1;
            $this->i = 0;
        }
        public function next() {
            $tmp = $this->b;
            $this->b = $this->a + $this->b;
            $this->a = $tmp;
            $this->i++;
        }
        public function valid() {
            return true;
        }
        public function key() {
            return $this->i;
        }
        public function current() {
            return $this->b;
        }
    }

Vous pouvez construire un itérateur qui va uniquement générer une valeur à chaque fois.
Le gros bénéfice est qu’on peut produire un nombre infini de valeurs, sans que la consommation mémoire augmente au fure et a mesure.

Un autre bénéfice d’utiliser un itérateur est que nous pouvons décorer ce dernier pour fournir des fonctionnalités additionnels.

Par exemple, si nous voulons itérer sur les valeurs paires sur le tableau precedent. Avec les tableaux, nous devons utiliser array_filter et donc dupliquer le tableau.
Par contre, avec les itérateurs, nous pouvons simplement utiliser la Classe core CallbackFilterIterator.

    $it = new CallbackFilterIterator($it, function($value) { return $value % 2 == 0; });

La partie cool ici est que la structure du tableau original n’est jamais changée ou dupliquée. L’Itérateur va uniquement utiliser la méthode next() sur l’itérateur core jusqu’à trouver la valeur voulue.

La librairie SPL de PHP contient un nombre interessant de ce type de décorateurs que vous pouvez utiliser pour optimiser le comportement de vos itérateurs.

Maintenant, vous avez peut etre remarqué que les itérateurs ne sont pas typiquement des petites classes, leur implementation est assez verbeuse.

Par ailleurs, vous pouvez donner le comportement “traversable” a votre classe dans une boucle foreach en implémentant IteratorAggregate :

    interface IteratorAggregate extends Traversable {
        public function getIterator();
    }

L’interface IteratorAggregate permet d’instancier un itérateur different de votre classe. Supposons que votre classe a un tableau que vous voulez boucler dessus.

Au lieu de construire un nouvel itérateur dans votre classe, il faut simplement implementer IteratorAggregate :

    class MyClass implements IteratorAggregate {
        protected $myArray = array();
        public function getIterator() {
            return new ArrayIterator($this->myArray);
        }
    }

Maintenant, nous pouvons utiliser une instance de notre classe dans un foreach!
Les itérateurs sont un concept tres puissant qui, si utilisé comme il faut, permettre d’avoir un code propre et flexible!

La video originale d’Anthony Ferrara (Anglais) : http://www.youtube.com/watch?v=tW6GcZjBc3E&feature=share&list=PLM-218uGSX3DQ3KsB5NJnuOqPqc5CW2kW

Anis Berejeb

Anis est avant tout un passioné de l'agilité et du développement. Avec plus de 15 ans dans le domaine du développement web, son expertise combine des connaissances accrues dans l'ensemble des notions partant du développement logiciel jusqu'à l'organisation des équipes dans les environnements agiles à grande échelle.

You may also like...

1 Response

  1. Mickey Neal dit :

    La classe Array comporte un certain nombre de méthodes dont plusieurs s’inspirent du langage Perl. Elles permettent de manipuler les tableaux en les transposant en chaînes de caractères, de les inverser, de les trier, d’en extraire des sous-tableaux, de les gérer en tant que pile ou file, etc.

Laisser un commentaire