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
yRecursiveIteratorIterator
?
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 cualquierTraversable
para el cruce lineal,RecursiveIteratorIterator
necesita unRecursiveIterator
más específico para recorrer un árbol. - Donde
IteratorIterator
expone suIterator
principal a través degetInnerIerator()
,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 porRecursiveIteratorIterator
). -
RecursiveIteratorIterator
tiene más métodos queIteratorIterator
.
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. .