self_first recursiveiteratoriterator recursive php iterator spl

self_first - ¿Cómo funciona RecursiveIteratorIterator en PHP?



php find file in directory recursive (4)

¿Cómo funciona RecursiveIteratorIterator ?

El manual de PHP no tiene mucho documentado o explicado. ¿Cuál es la diferencia entre IteratorIterator y RecursiveIteratorIterator ?


¿Cuál es la diferencia de IteratorIterator y RecursiveIteratorIterator ?

Para entender la diferencia entre estos dos iteradores, uno debe primero entender un poco sobre las convenciones de nomenclatura usadas y lo que queremos decir con iteradores "recursivos".

Iteradores recursivos y no recursivos

PHP tiene iteradores no "recursivos", como ArrayIterator y FilesystemIterator . También hay iteradores "recursivos" como RecursiveArrayIterator y RecursiveDirectoryIterator . Estos últimos tienen métodos que les permiten ser profundizados, los primeros no.

Cuando las instancias de estos iteradores se enlazan por sí mismas, incluso las recursivas, los valores solo provienen del nivel "superior", incluso si se repiten sobre una matriz anidada o un directorio con subdirectorios.

Los iteradores recursivos implementan comportamiento recursivo (a través de hasChildren() , getChildren() ) pero no lo explotan .

Puede ser mejor pensar en los iteradores recursivos como iteradores "recurrentes", tienen la capacidad de ser iterados recursivamente, pero simplemente iterar sobre una instancia de una de estas clases no hará eso. Para explotar el comportamiento recursivo, sigue leyendo.

RecursiveIteratorIterator

Aquí es donde entra en juego el RecursiveIteratorIterator . Tiene el conocimiento de cómo llamar a los iteradores "recurrentes" de forma que se profundice en la estructura en un bucle normal, plano. Pone el comportamiento recursivo en acción. Básicamente hace el trabajo de pasar sobre cada uno de los valores en el iterador, mirando para ver si hay "niños" en los que recurrir o no, y entrando y saliendo de esas colecciones de niños. Coloca una instancia de RecursiveIteratorIterator en un foreach y se sumerge en la estructura para que no sea necesario.

Si no se usó el RecursiveIteratorIterator , tendría que escribir sus propios bucles recursivos para explotar el comportamiento recursivo, verificando con hasChildren() del iterador "iterativo" y usando getChildren() .

Entonces, esa es una breve descripción de RecursiveIteratorIterator , ¿cómo es diferente de IteratorIterator ? Bueno, básicamente estás haciendo el mismo tipo de pregunta que ¿Cuál es la diferencia entre un gatito y un árbol? El hecho de que ambos aparezcan en la misma enciclopedia (o manual, para los iteradores) no significa que deba confundirse entre los dos.

IteratorIterator

El trabajo del IteratorIterator es tomar cualquier objeto Traversable y envolverlo de manera que satisfaga la interfaz Iterator . Un uso para esto es poder aplicar el comportamiento específico del iterador en el objeto no iterador.

Para dar un ejemplo práctico, la clase DatePeriod es Traversable pero no un Iterator . Como tal, podemos recorrer sus valores con foreach() pero no podemos hacer otras cosas que normalmente haríamos con un iterador, como el filtrado.

TAREA : Pasa el lunes, miércoles y viernes de las próximas cuatro semanas.

Sí, esto es trivial al DatePeriod en el DatePeriod y usar un if() dentro del ciclo; pero ese no es el punto de este ejemplo!

$period = new DatePeriod(new DateTime, new DateInterval(''P1D''), 28); $dates = new CallbackFilterIterator($period, function ($date) { return in_array($date->format(''l''), array(''Monday'', ''Wednesday'', ''Friday'')); }); foreach ($dates as $date) { … }

El fragmento anterior no funcionará porque CallbackFilterIterator espera una instancia de una clase que implemente la interfaz DatePeriod , y DatePeriod no lo hace. Sin embargo, dado que es Traversable , podemos satisfacer fácilmente ese requisito utilizando IteratorIterator .

$period = new IteratorIterator(new DatePeriod(…));

Como puede ver, esto no tiene nada que ver con iterar sobre las clases de iterador ni con la recursión, y ahí reside la diferencia entre IteratorIterator y RecursiveIteratorIterator .

Resumen

RecursiveIteraratorIterator es para iterar sobre un RecursiveIterator (iterador "recurrente"), explotando el comportamiento recursivo que está disponible.

IteratorIterator es para aplicar el comportamiento del iterador a objetos no iteradores, Traversable .


Cuando se usa con iterator_to_array() , RecursiveIteratorIterator caminará recursivamente la matriz para encontrar todos los valores. Lo que significa que aplanará la matriz original.

IteratorIterator mantendrá la estructura jerárquica original.

Este ejemplo le mostrará claramente la diferencia:

$array = array( ''ford'', ''model'' => ''F150'', ''color'' => ''blue'', ''options'' => array(''radio'' => ''satellite'') ); $recursiveIterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array)); var_dump(iterator_to_array($recursiveIterator, true)); $iterator = new IteratorIterator(new ArrayIterator($array)); var_dump(iterator_to_array($iterator,true));


RecursiveDirectoryIterator muestra la ruta completa y no solo el nombre del archivo. El resto se ve bien. Esto se debe a que los nombres de archivo son generados por SplFileInfo. Esos deberían mostrarse como el nombre base en su lugar. El resultado deseado es el siguiente:

$path =__DIR__; $dir = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS); $files = new RecursiveIteratorIterator($dir,RecursiveIteratorIterator::SELF_FIRST); while ($files->valid()) { $file = $files->current(); $filename = $file->getFilename(); $deep = $files->getDepth(); $indent = str_repeat(''│ '', $deep); $files->next(); $valid = $files->valid(); if ($valid and ($files->getDepth() - 1 == $deep or $files->getDepth() == $deep)) { echo $indent, "├ $filename/n"; } else { echo $indent, "└ $filename/n"; } }

salida:

tree ├ dirA │ ├ dirB │ │ └ fileD │ ├ fileB │ └ fileC └ fileA


RecursiveIteratorIterator es un Iterator concreto que implementa el Iterator árboles . Permite a un programador atravesar un objeto contenedor que implementa la interfaz RecursiveIterator ; consulte Iterator en Wikipedia para conocer los principios generales, los tipos, la semántica y los patrones de los iteradores.

En diferencia a IteratorIterator que es un Iterator concreto que implementa el recorrido de objetos en orden lineal (y acepta de forma predeterminada cualquier tipo de Traversable en su constructor), RecursiveIteratorIterator permite el bucle sobre todos los nodos en un árbol ordenado de objetos y su constructor toma un RecursiveIterator .

En resumen: RecursiveIteratorIterator permite recorrer un árbol, IteratorIterator permite recorrer una lista. Lo muestro con algunos ejemplos de código a continuación pronto.

Técnicamente, esto funciona rompiendo la linealidad atravesando todos los hijos de un nodo (si hay alguno). Esto es posible porque, por definición, todos los hijos de un nodo son nuevamente un RecursiveIterator . El Iterator de nivel Iterator apila internamente los diferentes RecursiveIterator por su profundidad y mantiene un puntero al Iterator activo actual para el recorrido.

Esto permite visitar todos los nodos de un árbol.

Los principios subyacentes son los mismos que con IteratorIterator : una interfaz especifica el tipo de iteración y la clase de iterador base es la implementación de esta semántica. Compare con los ejemplos a continuación, para el bucle lineal con foreach , normalmente no piensa mucho en los detalles de implementación a menos que necesite definir un nuevo Iterator (por ejemplo, cuando algún tipo concreto no implementa Traversable ).

Para el recorrido recursivo, a menos que no utilice un Traversal predefinido que ya tenga iteración transversal recursiva, normalmente necesita crear una instancia de la iteración RecursiveIteratorIterator existente o incluso escribir una iteración transversal recursiva que sea Traversable para que tenga este tipo de iteración transversal. con foreach .

Consejo: Probablemente usted no implementó uno ni otro, por lo que podría ser algo que valga la pena para su experiencia práctica de las diferencias que tienen. Encuentra una sugerencia de bricolaje al final de la respuesta.

Diferencias técnicas en resumen:

  • Mientras IteratorIterator toma cualquier Traversable para el cruce lineal, RecursiveIteratorIterator necesita un RecursiveIterator más específico para recorrer un árbol.
  • Donde IteratorIterator expone su Iterator principal a través de getInnerIerator() , RecursiveIteratorIterator proporciona el sub- Iterator activo actual solo a través de ese método.
  • Si bien IteratorIterator no tiene conocimiento de nada como padres o hijos, RecursiveIteratorIterator sabe cómo atravesar y atravesar a los niños.
  • IteratorIterator no necesita una pila de iteradores, RecursiveIteratorIterator tiene dicha pila y conoce el sub-iterador activo.
  • Donde IteratorIterator tiene su orden debido a la linealidad y no tiene opción, RecursiveIteratorIterator tiene la opción de recorrer más y necesita decidir por cada nodo (decidido a través del modo por RecursiveIteratorIterator ).
  • RecursiveIteratorIterator tiene más métodos que IteratorIterator .

Para resumir: RecursiveIterator es un tipo concreto de iteración (bucle sobre un árbol) que funciona en sus propios iteradores, es decir RecursiveIterator . Ese es el mismo principio subyacente que con IteratorIerator , pero el tipo de iteración es diferente (orden lineal).

Idealmente, puedes crear tu propio set también. Lo único necesario es que su iterador implemente Traversable que es posible a través de Iterator o IteratorAggregate . Entonces puedes usarlo con foreach . Por ejemplo, algún tipo de objeto iterativo recursivo de cruce de árbol ternario junto con la interfaz de iteración correspondiente para el objeto (s) contenedor.

Repasemos algunos ejemplos de la vida real que no son tan abstractos. Entre interfaces, iteradores concretos, objetos de contenedor y semántica de iteración, esta no es una mala idea.

Tome una lista de directorios como un ejemplo. Considere que tiene el siguiente árbol de archivos y directorios en el disco:

Mientras que un iterador con orden lineal solo recorre la carpeta y los archivos de toplevel (una sola lista de directorios), el iterador recursivo atraviesa también las subcarpetas y enumera todas las carpetas y archivos (una lista de directorios con listados de sus subdirectorios):

Non-Recursive Recursive ============= ========= [tree] [tree] ├ dirA ├ dirA └ fileA │ ├ dirB │ │ └ fileD │ ├ fileB │ └ fileC └ fileA

Puede comparar esto fácilmente con IteratorIterator que no tiene recursividad para recorrer el árbol de directorios. Y el RecursiveIteratorIterator que puede atravesar el árbol como muestra el listado recursivo.

Al principio, un ejemplo muy básico con un DirectoryIterator que implementa Traversable que permite a foreach iterar sobre él:

$path = ''tree''; $dir = new DirectoryIterator($path); echo "[$path]/n"; foreach ($dir as $file) { echo " ├ $file/n"; }

El resultado ejemplar para la estructura de directorios anterior es:

[tree] ├ . ├ .. ├ dirA ├ fileA

Como ve, esto aún no está utilizando IteratorIterator o RecursiveIteratorIterator . En su lugar, simplemente usa foreach que opera en la interfaz Traversable .

Como foreach por defecto solo conoce el tipo de iteración llamada orden lineal, es posible que deseemos especificar el tipo de iteración explícitamente. A primera vista, puede parecer demasiado detallado, pero para fines de demostración (y para hacer la diferencia con RecursiveIteratorIterator más visible más adelante), especifiquemos el tipo de iteración lineal que especifica explícitamente el tipo de iteración IteratorIterator para la lista del directorio:

$files = new IteratorIterator($dir); echo "[$path]/n"; foreach ($files as $file) { echo " ├ $file/n"; }

Este ejemplo es casi idéntico al primero, la diferencia es que $files es ahora un tipo de iteración Traversable para Traversable $dir :

$files = new IteratorIterator($dir);

Como de costumbre, el acto de iteración es realizado por el foreach :

foreach ($files as $file) {

La salida es exactamente la misma. Entonces, ¿qué es diferente? Diferente es el objeto utilizado dentro del foreach . En el primer ejemplo, es un DirectoryIterator en el segundo ejemplo es el IteratorIterator . Esto muestra la flexibilidad que tienen los iteradores: puede reemplazarlos entre sí, el código dentro de foreach simplemente continúa funcionando como se esperaba.

Vamos a comenzar a obtener la lista completa, incluidos los subdirectorios.

Como ahora hemos especificado el tipo de iteración, consideremos cambiarla a otro tipo de iteración.

Sabemos que ahora necesitamos atravesar todo el árbol, no solo el primer nivel. Para que funcione con un foreach simple, necesitamos un tipo diferente de iterador: RecursiveIteratorIterator . Y ese solo puede iterar sobre los objetos contenedores que tienen la interfaz RecursiveIterator .

La interfaz es un contrato. Cualquier clase que lo implemente se puede usar junto con el RecursiveIteratorIterator . Un ejemplo de dicha clase es el RecursiveDirectoryIterator , que es algo así como la variante recursiva de DirectoryIterator .

Veamos un primer ejemplo de código antes de escribir cualquier otra oración con la palabra I:

$dir = new RecursiveDirectoryIterator($path); echo "[$path]/n"; foreach ($dir as $file) { echo " ├ $file/n"; }

Este tercer ejemplo es casi idéntico al primero, sin embargo, crea un resultado diferente:

[tree] ├ tree/. ├ tree/.. ├ tree/dirA ├ tree/fileA

De acuerdo, no es tan diferente, el nombre de archivo ahora contiene el nombre de ruta al frente, pero el resto también se ve similar.

Como muestra el ejemplo, incluso el objeto de directorio ya incluye la interfaz RecursiveIterator , esto aún no es suficiente para hacer que foreach recorra todo el árbol de directorios. Aquí es donde entra en acción el RecursiveIteratorIterator . El ejemplo 4 muestra cómo:

$files = new RecursiveIteratorIterator($dir); echo "[$path]/n"; foreach ($files as $file) { echo " ├ $file/n"; }

El uso del RecursiveIteratorIterator lugar del objeto $dir anterior hará que foreach pueda recorrer todos los archivos y directorios de forma recursiva. A continuación, se enumeran todos los archivos, ya que el tipo de iteración de objetos se ha especificado ahora:

[tree] ├ tree/. ├ tree/.. ├ tree/dirA/. ├ tree/dirA/.. ├ tree/dirA/dirB/. ├ tree/dirA/dirB/.. ├ tree/dirA/dirB/fileD ├ tree/dirA/fileB ├ tree/dirA/fileC ├ tree/fileA

Esto ya debería demostrar la diferencia entre el recorrido plano y el transversal del árbol. El RecursiveIteratorIterator puede atravesar cualquier estructura en forma de árbol como una lista de elementos. Debido a que hay más información (como el nivel que ocupa la iteración actualmente), es posible acceder al objeto del iterador mientras se itera sobre él y, por ejemplo, sangrar el resultado:

echo "[$path]/n"; foreach ($files as $file) { $indent = str_repeat('' '', $files->getDepth()); echo $indent, " ├ $file/n"; }

Y salida del Ejemplo 5 :

[tree] ├ tree/. ├ tree/.. ├ tree/dirA/. ├ tree/dirA/.. ├ tree/dirA/dirB/. ├ tree/dirA/dirB/.. ├ tree/dirA/dirB/fileD ├ tree/dirA/fileB ├ tree/dirA/fileC ├ tree/fileA

Seguro que esto no gana un concurso de belleza, pero muestra que con el iterador recursivo hay más información disponible que solo el orden lineal de la clave y el valor . Incluso foreach solo puede expresar este tipo de linealidad, acceder al propio iterador permite obtener más información.

De forma similar a la metainformación, también existen diferentes formas de atravesar el árbol y, por lo tanto, ordenar el resultado. Este es el Modo del RecursiveIteratorIterator y se puede configurar con el constructor.

El siguiente ejemplo le dirá al RecursiveDirectoryIterator que elimine las entradas de puntos (y .. ) ya que no las necesitamos. Pero también se cambiará el modo de recursión para tomar primero el elemento padre (el subdirectorio) ( SELF_FIRST ) antes de los hijos (los archivos y subdirectorios en el subdirectorio):

$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS); $files = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::SELF_FIRST); echo "[$path]/n"; foreach ($files as $file) { $indent = str_repeat('' '', $files->getDepth()); echo $indent, " ├ $file/n"; }

El resultado ahora muestra las entradas del subdirectorio correctamente enumeradas, si compara con el resultado anterior, esas no estaban allí:

[tree] ├ tree/dirA ├ tree/dirA/dirB ├ tree/dirA/dirB/fileD ├ tree/dirA/fileB ├ tree/dirA/fileC ├ tree/fileA

El modo de recursión, por lo tanto, controla qué y cuándo se devuelve un filo o hoja en el árbol, para el ejemplo del directorio:

  • LEAVES_ONLY (predeterminado): solo lista archivos, no directorios.
  • SELF_FIRST (arriba): lista el directorio y luego los archivos allí.
  • CHILD_FIRST (sin ejemplo): Primero, CHILD_FIRST archivos en el subdirectorio, luego el directorio.

Salida del Ejemplo 5 con los otros dos modos:

LEAVES_ONLY CHILD_FIRST [tree] [tree] ├ tree/dirA/dirB/fileD ├ tree/dirA/dirB/fileD ├ tree/dirA/fileB ├ tree/dirA/dirB ├ tree/dirA/fileC ├ tree/dirA/fileB ├ tree/fileA ├ tree/dirA/fileC ├ tree/dirA ├ tree/fileA

Cuando se compara con el recorrido estándar, todas estas cosas no están disponibles. Por lo tanto, la iteración recursiva es un poco más compleja cuando necesitas guiñar la cabeza, sin embargo es fácil de usar porque se comporta como un iterador, lo pones en un foreach y listo.

Creo que estos son suficientes ejemplos para una respuesta. Puede encontrar el código fuente completo y un ejemplo para mostrar ascii-árboles bonitos en esta esencia: https://gist.github.com/3599532

Hágalo usted mismo: haga que el RecursiveTreeIterator trabaje línea por línea.

El ejemplo 5 demostró que hay una metainformación sobre el estado del iterador disponible. Sin embargo, esto se demostró a propósito dentro de la iteración foreach . En la vida real esto pertenece naturalmente dentro del RecursiveIterator .

Un mejor ejemplo es el RecursiveTreeIterator , se encarga de la sangría, el prefijo, etc. Vea el siguiente fragmento de código:

$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS); $lines = new RecursiveTreeIterator($dir); $unicodeTreePrefix($lines); echo "[$path]/n", implode("/n", iterator_to_array($lines));

El RecursiveTreeIterator está diseñado para trabajar línea por línea, la salida es bastante directa con un pequeño problema:

[tree] ├ tree/dirA │ ├ tree/dirA/dirB │ │ └ tree/dirA/dirB/fileD │ ├ tree/dirA/fileB │ └ tree/dirA/fileC └ tree/fileA

Cuando se usa en combinación con un RecursiveDirectoryIterator , muestra el nombre completo de la ruta y no solo el nombre del archivo. El resto se ve bien. Esto se debe a que los nombres de archivo son generados por SplFileInfo . Esos deberían mostrarse como el nombre base en su lugar. El resultado deseado es el siguiente:

/// Solved /// [tree] ├ dirA │ ├ dirB │ │ └ fileD │ ├ fileB │ └ fileC └ fileA

Cree una clase de decorador que se pueda usar con RecursiveTreeIterator lugar de RecursiveDirectoryIterator . Debería proporcionar el nombre base de la corriente SplFileInfo lugar de la ruta de acceso. El fragmento de código final podría verse así:

$lines = new RecursiveTreeIterator( new DiyRecursiveDecorator($dir) ); $unicodeTreePrefix($lines); echo "[$path]/n", implode("/n", iterator_to_array($lines));

Estos fragmentos, incluido $unicodeTreePrefix forman parte de la esencia del Apéndice: Hágalo usted mismo: haga que el RecursiveTreeIterator trabaje línea por línea. .