Grâce à ce mécanisme, il est possible de faire des choses de ce style :


class myDirectory implements iterator
{
...
}

$directory = new myDirectory('/tmp');
foreach ($directory as $inode)
{
echo $inode;
}

Cerise sur le gâteau, il est également possible de filtrer ce qui est renvoyé par un objet de la classe iterator à l'aide de l'itérateur filterIterator, de la manière suivante :

class myDirectoryFilter extends filterIterator
{
...
}

$directory = new myDirectory('/tmp');
foreach (new myDirectoryFilter($directory) as $inode)
{
echo $inode;
}

C'est évidement très puissant.

Cependant, dans certain cas, il y a un problème.

Nous allons par exemple ajouter dans la classe myClass une méthode getFromDirectory qui prend en argument un objet de la classe myDirectory et dont le but est d'effectuer un traitement, peu importe lequel, sur chaque fichier renvoyé par cet objet.

Son code est le suivant :

class myClass
{
public function getFromDirectory(myDirectory $directory)
{
if ($directory->exists())
{
foreach ($directory as $inode)
{
...
}
}
}
}

Tout va bien, sauf si nous souhaitons laisser au développeur la possibilité de filter ce que renvoie l'itérateur myDirectory, indépendament de cet itérateur.

En effet, notre définition de la méthode oblige l'argument à être de la classe myDirectory et non de la classe filterIterator.

Qu'à cela ne tienne, nous allons définir un constructeur qui permet la définition du filtre à employer, et nous allons modifier la méthode myClass::getFromDirectory() de la manière suivante :

class myClass
{
protected $filter = null;

public function __construct(filterIterator $filter)
{
$this->setFilter($filter);
}

public function setFilter(filterIterator $filter)
{
$this->filter = $filter;
return $this;
}

public function getFromDirectory(myDirectory $directory)
{
if ($directory->exists())
{

$this->filter->setInnerIterator($directory);
foreach ($this->filter as $inode)
{
...
}
}
}

...
}

Sauf que cela n'est pas possible de par la conception de la classe filterIterator...

En effet, son constructeur impose que son argument soit un objet de la classe iterator.

Cela revient donc à dire qu'il faut connaître à l'avance ce qui doit être filtré, ce qui n'est pas notre cas.

De plus, la méthode filterIterator::setInnerIterator() n'existe pas.

Cerise sur le gâteau, la propriété filterIterator::$innerIterator est une propriété privée de la classe, ce qui fait qu'il n'est pas possible de contourner le problème en dérivant filterIterator.

Quelle est la solution, alors ?

Modifier notre définition de myClass::getFromDirectory() pour qu'elle accepte un objet de la classe filterIterator ? Ce n'est pas jouable, puisque nous voulons pouvoir utiliser des méthodes appartenant à la classe myDirectory dans cette méthode, en l'occurence myDirectory::exists().

Modifier la classe myDirectory pour y encapsuler le filtrage ? Mauvaise idée, car pour chaque filtrage, il faudrait dériver la classe myDirectory.

Je me suis finalement résigné à écrire ma propre classe afin de faire ce filtrage, en me basant sur le code source de filterIterator afin de pouvoir découpler complétement le filtrage de l'itération.

J'ai ainsi rajouté une méthode myFilterIterator::setInnerIterator() et j'ai modifié la signature du constructeur pour qu'il accepte la valeur null par défaut.

J'ai donc dupliqué au moins 90% du code de filterIterator, ce qui nous éloigne des bonnes pratiques... mais après tout, comme me l'a fait remarquer Jean-Marc, déclarer une propriété d'une classe pouvant être dérivée comme privée ne fait déjà pas partie des bonnes pratiques.

Mais si par hasard, vous avez une solution qui permette de découpler le filtrage de l'itération dans le cadre de cette problèmatique, en utilisant les outils natifs de la SPL, j'en serais très heureux, et peut être que cela me fera penser moins souvent, que PHP, c'est...

Et si, en plus, vous savez comment faire remonter ce genre de problème à l'auteur de la SPL, j'en serais encore plus heureux, car le bug tracker ne semble pas être le meilleur endroit pour ce genre de choses.