Iteradores en PHP

Iterador es uno de los patrones de diseño más conocidos, gracias al uso que hacen de él distintos lenguajes de programación, como Python, Java, C++ o el propio PHP. Básicamente, lo que propone este patrón es trasladar la responsabilidad de recorrer una colección a una clase nueva, que debe utilizar una interfaz estándar definida en una clase abstracta o una interfaz, de forma que se pueda recorrer de forma similar tuplas de la base de datos, nodos de un documento XML, elementos de un array, o cualquier otro tipo de colección que se nos ocurra.

Para crear nuestro iterador en PHP tenemos que / podemos implementar la interfaz Iterator. Esto nos permitirá recorrer nuestra colección utilizando un foreach, por ejemplo. La interfaz Iterator declara los siguientes métodos:

  • abstract public void rewind(void): debe mover el puntero al primer elemento, sin devolver ningún valor
  • abstract public mixed current(void): debe devolver el valor actual
  • abstract public scalar key(void): devuelve la clave actual
  • abstract public void next(void): mueve el puntero al siguiente elemento
  • abstract public boolean valid(void): devuelve true si el iterador sigue siendo válido (no se ha alcanzado el final de la colección) y false en caso contrario

Como ejemplo de iterador en PHP, vamos a implementar algo parecido a la clase DatePeriod de PHP 5.3, que permite iterar sobre los días de un periodo de fechas

<?php
class Intervalo {
    public $inicio;
    public $fin;


    function __construct($inicio, $fin) {
        $this->inicio = strtotime($inicio);
        $this->fin = strtotime($fin);
    }
}
<?php
class IteradorPorDia implements Iterator {
	private $intervalo;


	function __construct(Intervalo $intervalo) {
		$this->intervalo = $intervalo;
	}


    function rewind() {
        $this->claveActual = 1;
        $this->fechaActual = $this->intervalo->inicio;
    }


    function key() {
        return $this->claveActual;
    }


    function current() {
        return date('Y-m-d', $this->fechaActual);
    }


    function next() {
        $this->claveActual++;
        $this->fechaActual = strtotime('+1 day', $this->fechaActual);
    }


    function valid() {
        return ($this->fechaActual <= $this->intervalo->fin);
    }
}
<?php
require_once 'Intervalo.php';
require_once 'IteradorPorDia.php';

$intervalo = new Intervalo('2012-12-24', '2013-01-08');
$iterador = new IteradorPorDia($intervalo);
foreach($iterador as $num => $fecha)
	echo "El día $num del intervalo es $fecha<br/>";

Si queremos seguir el patrón Iterador al pie de la letra (recomendado), haciendo que sea el agregado el que cree el iterador y lo devuelva, nuestra colección tendrá que implementar la interfaz IteratorAggregate y su método abstract public Traversable getIterator(void). Si hacemos esto podremos pasar la colección al foreach directamente, haciendo nuestro código cliente más sencillo y claro.

<?php
require_once 'IteradorPorDia.php';

class Intervalo implements IteratorAggregate {
    public $inicio;
    public $fin;


    function __construct($inicio, $fin) {
        $this->inicio = strtotime($inicio);
        $this->fin = strtotime($fin);
    }


    public function getIterator() {
        return new IteradorPorDia($this);
    }
}
<?php
require_once 'Intervalo.php';

$intervalo = new Intervalo('2012-12-24', '2013-01-08');
foreach($intervalo as $num => $fecha)
	echo "El día $num del intervalo es $fecha
";

La librería estándar de PHP (SPL) también cuenta con varios iteradores predefinidos que pueden ser interesantes, además de una serie de clases que permiten encadenar varios iteradores (AppendIterator), filtrar valores no deseados (FilterIterator), iterar indefinidamente (InfiniteIterator), iterar sobre un subgrupo de elementos (LimitIterator) o iterar de forma recursiva (RecursiveIteratorIterator).

Comentarios
  1. Adrián

    Echaba en falta entradas como esta. Muy interesante el uso de iteradores en PHP.

    Saludos!

    Responder

  2. ¡Gracias Zootropo! Esta clase de entradas hace que vaya recordando todas esas prácticas que debería recordar (valga la redundancia) de EDA, Programación, etc.

    Responder

  3. Bastante útil y bien explicado. He visto unos cuantos artículos por ahí y la verdad que no acababa de pillarlo bien.

    Responder

  4. LMBRTH

    Muy bien explicado y de gran utilidad. Saludos desde Cancun, Mexico.

    Responder

  5. esta bueno el post, pero hace tiempos que no publican nada sobre python, hace mucha falta!!!

    Responder

  6. Muy buena entrada. Pese a que considero PHP más un DSL para ejemplo de SQLInjection y XSS es una maldición obligada en muchos casos.

    Sin duda una interfaz que nos permita un Iterable (como lo hace Java o con IEnumerable en .NET) nos brinda bastante flexibilidad a la hora de pedir datos desde nuestras estructuras de datos, en especial las que guardamos en caché (aunque al recibir listas/tupas desde la bd, en ocasiones necesitamos “iterar por”…incluso una interfaz de ordenamiento viene perlas…volviendo al tema) para no perder el tiempo creando clases y usando el principio “delegar antes de heredar”, sobretodo porqué de soportar o no la herencia múltiple, todo después es un soberano desastre y al final no se sabe de donde viene nada.

    Sin duda, me parece interesante, pero tengo una duda. Siendo PHP un lenguaje de scripting, ¿realmente necesita tener interfaces a lo C# o Java?… Tengo entendido que es la por la qué en Python las interfaces no son iguales a las de Java, porqué no se necesita un “contrato” de interfaz debido a que todo lo llevamos a runtime. De lo contrario ya sé que PHP 6 será algo similar a Java 6 o con esperanzas a Java 7 (que parece que es el rumbo al que apunta PHP desde hace algún tiempo).

    En fin, espero puedan despejar mis dudas.

    Responder

    • Necesitarlas, no las necesita, porque como bien dices los tipos son mucho más flexibles (para lo bueno y para lo malo). Pero no está de más hacer el contrato un poco más explícito.

      Responder

Deja un comentario