¿Cómo funciona RecursiveIteratorIterator en PHP?

88

¿Cómo RecursiveIteratorIteratorfunciona?

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

varuog
fuente
2
hay un ejemplo en php.net/manual/en/recursiveiteratoriterator.construct.php y también hay una Introducción en php.net/manual/en/class.iteratoriterator.php - ¿puede señalar qué es exactamente lo que tiene problemas para entender . ¿Qué debe contener el manual para que sea más fácil de entender?
Gordon
1
Si pregunta cómo RecursiveIteratorIteratorfunciona, ¿ya ha entendido cómo IteratorIteratorfunciona? Quiero decir que es básicamente lo mismo, solo que la interfaz que consumen los dos es diferente. ¿Está más interesado en algunos ejemplos o desea ver las diferencias de la implementación del código C subyacente?
hakre
@Gordon no estaba seguro de cómo un solo bucle foreach puede atravesar todos los elementos en la estructura del árbol
varuog
@hakra Ahora estoy tratando de estudiar todas las interfaces integradas, así como la implementación de la interfaz spl y el iterador. Estaba interesado en saber cómo funciona en segundo plano con el bucle forach con algunos ejemplos.
varuog
@hakre En realidad, ambos son muy diferentes. IteratorIteratormapas Iteratory IteratorAggregateen un Iterator, donde REcusiveIteratorIteratorse usa para atravesar recusivamente aRecursiveIterator
Adán

Respuestas:

249

RecursiveIteratorIteratores un cruce de árbol deIterator implementación concreto . Permite a un programador atravesar un objeto contenedor que implementa la interfaz, consulteRecursiveIterator Iterador en Wikipedia para conocer los principios generales, tipos, semántica y patrones de los iteradores.

A diferencia de lo IteratorIteratorque es un Iteratorobjeto transversal de implementación concreto en orden lineal (y por defecto acepta cualquier tipo de Traversableen su constructor), RecursiveIteratorIteratorpermite recorrer todos los nodos en un árbol ordenado de objetos y su constructor toma unRecursiveIterator .

En resumen: le RecursiveIteratorIteratorpermite recorrer un árbol, le IteratorIteratorpermite recorrer una lista. Pronto lo muestro con algunos ejemplos de código a continuación.

Técnicamente, esto funciona rompiendo la linealidad atravesando todos los hijos de un nodo (si los hay). Esto es posible porque, por definición, todos los hijos de un nodo son nuevamente a RecursiveIterator. El nivel superior Iteratorluego apila internamente los diferentes RecursiveIterators por su profundidad y mantiene un puntero al sub actual activo Iteratorpara 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 foreachusted normalmente no piense mucho en los detalles de implementación a menos que necesite definir un nuevo Iterator(por ejemplo, cuando algún tipo concreto en sí mismo no se implementaTraversable ).

Para el recorrido recursivo, a menos que no use una Traversaliteración de recorrido recursiva predefinida , normalmente necesita crear una instancia de la RecursiveIteratorIteratoriteración existente o incluso escribir una iteración de recorrido recursiva que sea Traversablesuya para tener este tipo de iteración de recorrido foreach.

Propina: probablemente no implementó uno ni el otro por su cuenta, por lo que esto podría ser algo que valga la pena hacer para su experiencia práctica de las diferencias que tienen. Encontrará una sugerencia de bricolaje al final de la respuesta.

Diferencias técnicas en resumen:

  • Si bien IteratorIteratortoma cualquiera Traversablepara el recorrido lineal,RecursiveIteratorIterator necesita uno más específico RecursiveIteratorpara recorrer un árbol.
  • Donde IteratorIteratorexpone su Iteratorvía principal getInnerIerator(),RecursiveIteratorIterator proporciona el sub-activo actual Iteratorsólo a través de ese método.
  • Si bien IteratorIteratorno tiene conocimiento de nada como padres o hijos,RecursiveIteratorIterator sabe cómo atrapar y atravesar a los niños.
  • IteratorIterator no necesita una pila de iteradores, RecursiveIteratorIterator tiene dicha pila y conoce el sub-iterador activo.
  • Donde IteratorIteratortiene su orden debido a la linealidad y no hay opción, RecursiveIteratorIteratortiene una opción para un mayor recorrido y necesita decidir por cada nodo (decidido a través del modo porRecursiveIteratorIterator ).
  • RecursiveIteratorIteratortiene más métodos que IteratorIterator.

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

Idealmente, también puede crear su propio conjunto. Lo único necesario es que su iterador implemente lo Traversableque es posible a través de Iteratoro IteratorAggregate. Entonces puedes usarlo con foreach. Por ejemplo, algún tipo de objeto de iteración recursiva transversal de árbol ternario junto con la interfaz de iteración correspondiente para los objetos de contenedor.


Repasemos con algunos ejemplos de la vida real que no son tan abstractos. Entre interfaces, iteradores concretos, objetos contenedores y semántica de iteración, esto tal vez no sea una mala idea.

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

Árbol de directorios

Mientras que un iterador con orden lineal simplemente atraviesa la carpeta y los archivos de nivel superior (una lista de un solo directorio), el iterador recursivo también atraviesa las subcarpetas y enumera todas las carpetas y archivos (una lista de directorios con listas de sus subdirectorios):

Non-Recursive        Recursive
=============        =========

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

Puede compararlo fácilmente con el IteratorIteratorque no tiene recursividad para recorrer el árbol de directorios. Y elRecursiveIteratorIterator que puede atravesar el árbol como muestra el listado recursivo.

En un primer momento un ejemplo muy básico, con una DirectoryIteratorque implementa Traversablelo que permite foreacha iterar sobre ella:

$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 puede ver, esto aún no está usando IteratorIteratoro RecursiveIteratorIterator. En su lugar, solo usa foreachque opera en elTraversable interfaz.

Como foreachde forma predeterminada solo conoce el tipo de iteración denominado 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 RecursiveIteratorIteratormás visible más adelante), especifiquemos el tipo lineal de iteración especificando explícitamente el IteratorIteratortipo de iteración para la lista de directorios:

$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 $filesahora es un IteratorIteratortipo de iteración para Traversable $dir:

$files = new IteratorIterator($dir);

Como de costumbre, el acto de iteración lo realiza foreach:

foreach ($files as $file) {

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

Comencemos a obtener la lista completa, incluidos los subdirectorios.

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

Sabemos que debemos atravesar todo el árbol ahora, no solo el primer nivel. Para tener ese trabajo con un simple foreachnecesitamos un tipo diferente de iterador: RecursiveIteratorIterator. Y eso solo puede iterar sobre objetos contenedores que tienen la RecursiveIteratorinterfaz .

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

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 una salida diferente:

[tree]
  tree\.
  tree\..
  tree\dirA
  tree\fileA

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

Como muestra el ejemplo, incluso el objeto de directorio ya implementa la RecursiveIteratorinterfaz, esto aún no es suficiente para foreachrecorrer todo el árbol de directorios. Aquí es donde RecursiveIteratorIteratorentra en acción. El ejemplo 4 muestra cómo:

$files = new RecursiveIteratorIterator($dir);

echo "[$path]\n";
foreach ($files as $file) {
    echo " ├ $file\n";
}

Usar el en RecursiveIteratorIteratorlugar de solo el $dirobjeto anterior hará que se foreachrecorran todos los archivos y directorios de forma recursiva. Esto luego enumera todos los archivos, ya que ahora se ha especificado el tipo de iteración del objeto:

[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 recorrido plano y árbol. El RecursiveIteratorIteratorpuede atravesar cualquier estructura en forma de árbol como una lista de elementos. Debido a que hay más información (como el nivel en el que tiene lugar actualmente la iteración), es posible acceder al objeto iterador mientras se itera sobre él y, por ejemplo, sangrar la salida:

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 clave y valor . Incluso foreachsolo se puede expresar este tipo de linealidad, acceder al propio iterador permite obtener más información.

De manera similar a la metainformación, también hay diferentes formas posibles de atravesar el árbol y, por lo tanto, ordenar la salida. Este es el modo deRecursiveIteratorIterator y se puede configurar con el constructor.

El siguiente ejemplo le dirá a la RecursiveDirectoryIteratorque elimine las entradas de puntos ( .y ..) ya que no las necesitamos. Pero también se cambiará el modo de recursividad para tomar el elemento padre (el subdirectorio) primero ( SELF_FIRST) antes que los hijos (los archivos y subdirectorios del 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";
}

La salida ahora muestra las entradas del subdirectorio correctamente enumeradas, si compara con la salida anterior, las que no estaban allí:

[tree]
  tree\dirA
     tree\dirA\dirB
        tree\dirA\dirB\fileD
     tree\dirA\fileB
     tree\dirA\fileC
  tree\fileA

Por lo tanto, el modo de recursividad controla qué y cuándo se devuelve una rama u hoja en el árbol, para el ejemplo del directorio:

  • LEAVES_ONLY (predeterminado): solo enumera archivos, no directorios.
  • SELF_FIRST (arriba): lista el directorio y luego los archivos allí.
  • CHILD_FIRST (sin ejemplo): enumere los archivos en el subdirectorio primero y luego en 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 compara eso 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 entenderla, sin embargo, es fácil de usar porque se comporta como un iterador, lo pones en ay foreachlisto.

Creo que estos son ejemplos suficientes para una respuesta. Puede encontrar el código fuente completo, así como un ejemplo para mostrar árboles ascii de buen aspecto en esta esencia: https://gist.github.com/3599532

Hágalo usted mismo: haga el RecursiveTreeIteratortrabajo línea por línea.

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

Un mejor ejemplo es el RecursiveTreeIterator, se encarga de sangrar, prefijar, 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 RecursiveTreeIteratorestá destinado a la línea de trabajo de la línea, la salida es bastante sencillo 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 luce bien. Esto se debe a que los nombres de archivo son generados por SplFileInfo. En su lugar, deben mostrarse como el nombre de base. La salida deseada es la siguiente:

/// Solved ///

[tree]
  dirA
   dirB
    fileD
   fileB
   fileC
  fileA

Cree una clase de decorador que se pueda usar con en RecursiveTreeIteratorlugar de RecursiveDirectoryIterator. Debe proporcionar el nombre base de la actual en SplFileInfolugar del nombre de la ruta. 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 incluidos $unicodeTreePrefixson parte de la esencia del Apéndice: Hágalo usted mismo: Haga el RecursiveTreeIteratortrabajo línea por línea. .

hakre
fuente
No responde a las preguntas planteadas, contiene errores fácticos y pierde puntos centrales cuando pasa a personalizar la iteración. Con todo, parece un mal intento de obtener la recompensa por un tema del que no sabe mucho o, si lo sabe, no puede resumir en una respuesta a las preguntas planteadas.
salathe
2
Bueno, lo que no responde a mi pregunta de "por qué", simplemente crea más palabras sin decir mucho. ¿Quizás empieces con un error que cuenta? Apúntelo, no lo mantenga en secreto.
hakre
La primera oración es incorrecta: "RecursiveIteratorIterator es un IteratorIterator que admite ...", esto no es cierto.
salathe
1
@salathe: Tanque para sus comentarios. Edité la respuesta para abordarla. De hecho, la primera oración era incorrecta e incompleta. Todavía he omitido los detalles concretos de implementación RecursiveIteratorIteratorporque esto es común con otros tipos, pero proporcioné información técnica sobre cómo funciona realmente. Creo que los ejemplos muestran bien las diferencias: el tipo de iteración es la principal diferencia entre los dos. No tengo idea de que si compras el tipo de iteración, lo acuñas de manera un poco diferente, pero en mi humilde opinión no es fácil con la semántica que tienen los tipos de iteración.
Hakre
1
La primera parte se ha mejorado un poco, pero una vez que comienza a pasar a los ejemplos, todavía se desvían hacia inexactitudes fácticas. Si recortara la respuesta en la regla horizontal, se mejoraría mucho.
salathe
31

¿Cuál es la diferencia de IteratorIteratory RecursiveIteratorIterator?

Para comprender la diferencia entre estos dos iteradores, primero se debe comprender un poco acerca de las convenciones de nomenclatura utilizadas y lo que entendemos por iteradores "recursivos".

Iteradores recursivos y no recursivos

PHP tiene iteradores no "recursivos", como ArrayIteratory FilesystemIterator. También hay iteradores "recursivos" como RecursiveArrayIteratory RecursiveDirectoryIterator. Los últimos tienen métodos que les permiten profundizar, los primeros no.

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

Los iteradores recursivos implementan un comportamiento recursivo (vía hasChildren(), getChildren()) pero no lo explotan .

Podría ser mejor pensar en los iteradores recursivos como iteradores "recurrentes", tienen la capacidad de ser iterados de manera recursiva 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 RecursiveIteratorIteratorentra en juego. Tiene el conocimiento de cómo llamar a los iteradores "recurrentes" de tal manera que profundice en la estructura en un bucle normal y plano. Pone en acción el comportamiento recursivo. Básicamente hace el trabajo de pasar por encima de cada uno de los valores en el iterador, buscando ver si hay "niños" en los que recurrir o no, y entrar y salir de esas colecciones de niños. Pegas una instancia de RecursiveIteratorIteratoren un foreach y se sumerge en la estructura para que tú no tengas que hacerlo.

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

Así que esa es una breve descripción general de RecursiveIteratorIterator, ¿en qué se diferencia 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.

IteradorIterador

El trabajo del IteratorIteratores tomar cualquier Traversableobjeto y envolverlo de manera que satisfaga la Iteratorinterfaz. Un uso de esto es poder aplicar el comportamiento específico del iterador en el objeto no iterador.

Para dar un ejemplo práctico, la DatePeriodclase es Traversablepero 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 filtrar.

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

Sí, esto es trivial al foreachpasar sobre el DatePeriody usar un if()dentro del ciclo; ¡pero ese no es el objetivo 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 de código anterior no funcionará porque CallbackFilterIteratorespera una instancia de una clase que implemente la Iteratorinterfaz, que DatePeriodno 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 clases de iterador ni recursividad, y ahí radica la diferencia entre IteratorIteratory RecursiveIteratorIterator.

Resumen

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

IteratorIteratores para aplicar Iteratorcomportamiento a Traversableobjetos que no son iteradores .

salathe
fuente
¿No es IteratorIteratorsolo el tipo estándar de recorrido de orden lineal para Traversableobjetos? ¿Aquellos que podrían usarse sin él tal cual foreach? Y aún más, ¿no es RecursiveIterator siempre un Traversabley por lo tanto no solo IteratorIteratorsino también RecursiveIteratorIteratorsiempre "para aplicar Iteratorcomportamiento a objetos no iteradores, Traversables" ? (Ahora diría que foreachaplica el tipo de iteración a través del objeto iterador en objetos contenedor que implementan una interfaz de tipo iterador, por lo que estos son objetos contenedor-iterador, siempre Traversable)
hakre
Como dice mi respuesta, IteratorIteratores una clase que se trata de envolver Traversableobjetos en un Iterator. Nada más . Parece que está aplicando el término de manera más general.
salathe
Respuesta aparentemente informativa. Una pregunta, ¿no RecursiveIteratorIterator también envolvería los objetos para que también tuvieran acceso al comportamiento de Iterator? La única diferencia entre los dos sería que RecursiveIteratorIterator puede profundizar, mientras que IteratorIterator no puede.
Mike Purcell
@salathe, ¿sabe por qué el iterador recursivo (RecursiveDirectoryIterator) no implementa el comportamiento hasChildren (), getChildren ()?
anru
7
+1 por decir "recurrente". El nombre me extravió durante mucho tiempo porque Recursiveen RecursiveIteratorimplica comportamiento, mientras que un nombre más adecuado hubiera sido uno que describa la capacidad, como RecursibleIterator.
cabra
0

Cuando se usa con iterator_to_array(), RecursiveIteratorIteratorrecorrerá 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));
Tchoupi
fuente
Esto es totalmente engañoso. new IteratorIterator(new ArrayIterator($array))es equivalente a new ArrayIterator($array), es decir, lo externo IteratorIteratorno hace nada. Además, el aplanamiento de la salida no tiene nada que ver con iterator_to_arrayeso, simplemente convierte el iterador en una matriz. El aplanamiento es una propiedad de la forma en que RecursiveArrayIteratorcamina su iterador interno.
Preguntas de Quolonel
0

RecursiveDirectoryIterator muestra el nombre completo de la ruta y no solo el nombre del archivo. El resto luce bien. Esto se debe a que los nombres de archivo son generados por SplFileInfo. En su lugar, deben mostrarse como el nombre de base. La salida deseada es la 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
javad shariaty
fuente