Finder: búsqueda de archivos

¿Necesitas encontrar archivos que coincidan con una máscara determinada? Finder te ayudará con eso. Es una herramienta versátil y rápida para navegar por la estructura de directorios.

Instalación:

composer require nette/utils

Los ejemplos asumen que se ha creado un alias:

use Nette\Utils\Finder;

Uso

Primero, mostraremos cómo puedes usar Nette\Utils\Finder para listar los nombres de los archivos con extensiones .txt y .md en el directorio actual:

foreach (Finder::findFiles(['*.txt', '*.md']) as $name => $file) {
	echo $file;
}

El directorio predeterminado para la búsqueda es el directorio actual, pero puedes cambiarlo usando los métodos in() o from(). La variable $file es una instancia de la clase FileInfo con muchas métodos útiles. En la clave $name está la ruta al archivo como una cadena.

¿Qué buscar?

Además del método findFiles(), también existe findDirectories(), que busca solo directorios, y find(), que busca ambos. Estos métodos son estáticos, por lo que se pueden llamar sin crear una instancia. El parámetro con la máscara es opcional, si no lo especificas, se buscará todo.

foreach (Finder::find() as $file) {
	echo $file; // ahora se imprimirán todos los archivos y directorios
}

Con los métodos files() y directories() puedes especificar qué más buscar. Los métodos se pueden llamar repetidamente y como parámetro también se puede pasar un array de máscaras:

Finder::findDirectories('vendor') // todos los directorios
	->files(['*.php', '*.phpt']); // más todos los archivos PHP

Una alternativa a los métodos estáticos es crear una instancia usando new Finder (un objeto recién creado de esta manera no busca nada) y especificar qué buscar usando files() y directories():

(new Finder)
	->directories()      // todos los directorios
	->files('*.php');    // más todos los archivos PHP

En la máscara puedes usar los comodines *, **, ? y [...]. Incluso puedes especificar directorios, por ejemplo, src/*.php buscará todos los archivos PHP en el directorio src.

Los enlaces simbólicos también se consideran directorios o archivos.

¿Dónde buscar?

El directorio predeterminado para la búsqueda es el directorio actual. Puedes cambiarlo usando los métodos in() y from(). Como se desprende de los nombres de los métodos, in() busca solo en el directorio dado, mientras que from() busca también en sus subdirectorios (recursivamente). Si quieres buscar recursivamente en el directorio actual, puedes usar from('.').

Estos métodos se pueden llamar varias veces o pasarles varias rutas como un array, los archivos se buscarán entonces en todos los directorios. Si alguno de los directorios no existe, se lanzará una excepción Nette\UnexpectedValueException.

Finder::findFiles('*.php')
	->in(['src', 'tests']) // busca directamente en src/ y tests/
	->from('vendor');      // busca también en los subdirectorios de vendor/

Las rutas relativas son relativas al directorio actual. Por supuesto, también se pueden especificar rutas absolutas:

Finder::findFiles('*.php')
	->in('/var/www/html');

En la ruta es posible usar los comodines comodines *, **, ?. Así, por ejemplo, con la ruta src/*/*.php puedes buscar todos los archivos PHP en los directorios de segundo nivel dentro del directorio src. El carácter **, llamado globstar, es una baza poderosa, ya que permite buscar también en subdirectorios: con src/**/tests/*.php buscas todos los archivos PHP en el directorio tests ubicado en src o en cualquiera de sus subdirectorios.

Por el contrario, los comodines [...] no están soportados en la ruta, es decir, no tienen un significado especial, para evitar comportamientos no deseados en caso de que busques, por ejemplo, in(__DIR__) y casualmente en la ruta aparezcan los caracteres [].

Al buscar archivos y directorios en profundidad, primero se devuelve el directorio padre y luego los archivos contenidos en él, lo cual se puede invertir usando childFirst().

Comodines

En la máscara puedes usar varios caracteres especiales:

  • * – reemplaza cualquier número de caracteres cualesquiera (excepto /)
  • ** – reemplaza cualquier número de caracteres cualesquiera incluyendo / (es decir, se puede buscar multinivel)
  • ? – reemplaza un carácter cualquiera (excepto /)
  • [a-z] – reemplaza un carácter de la lista de caracteres entre corchetes
  • [!a-z] – reemplaza un carácter fuera de la lista de caracteres entre corchetes

Ejemplos de uso:

  • img/?.png – archivos con nombre de una sola letra 0.png, 1.png, x.png, etc.
  • logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log – logs en formato YYYY-MM-DD
  • src/**/tests/* – archivos en el directorio src/tests, src/foo/tests, src/foo/bar/tests y así sucesivamente.
  • docs/**.md – todos los archivos con extensión .md en todos los subdirectorios del directorio docs

Exclusión

Con el método exclude() puedes excluir archivos y directorios de la búsqueda. Especificas una máscara que el archivo no debe cumplir. Ejemplo de búsqueda de archivos *.txt excepto aquellos que contienen la letra X en el nombre:

Finder::findFiles('*.txt')
	->exclude('*X*');

Para omitir la navegación por subdirectorios, usa exclude():

Finder::findFiles('*.php')
	->from($dir)
	->exclude('temp', '.git')

Filtrado

Finder ofrece varios métodos para filtrar los resultados (es decir, reducirlos). Puedes combinarlos y llamarlos repetidamente.

Con size() filtramos por tamaño de archivo. Así encontramos archivos con un tamaño en el rango de 100 a 200 bytes:

Finder::findFiles('*.php')
	->size('>=', 100)
	->size('<=', 200);

El método date() filtra por fecha de última modificación del archivo. Los valores pueden ser absolutos o relativos a la fecha y hora actuales, por ejemplo, así encontramos archivos modificados en las últimas dos semanas:

Finder::findFiles('*.php')
	->date('>', '-2 weeks')
	->from($dir)

Ambas funciones entienden los operadores >, >=, <, <=, =, !=, <>.

Finder también permite filtrar los resultados usando funciones personalizadas. La función recibe como parámetro un objeto Nette\Utils\FileInfo y debe devolver true para que el archivo sea incluido en los resultados.

Ejemplo: búsqueda de archivos PHP que contienen la cadena Nette (independientemente de mayúsculas/minúsculas):

Finder::findFiles('*.php')
	->filter(fn($file) => strcasecmp($file->read(), 'Nette') === 0);

Filtrado en profundidad

Durante la búsqueda recursiva, puedes establecer la profundidad máxima de navegación usando el método limitDepth(). Si estableces limitDepth(1), solo se navega por los primeros subdirectorios, limitDepth(0) desactiva la navegación en profundidad y el valor –1 cancela el límite.

Finder permite, usando funciones personalizadas, decidir en qué directorio entrar durante la navegación. La función recibe como parámetro un objeto Nette\Utils\FileInfo y debe devolver true para entrar en el directorio:

Finder::findFiles('*.php')
	->descentFilter($file->getBasename() !== 'temp');

Ordenación

Finder también ofrece varias funciones para ordenar los resultados.

El método sortByName() ordena los resultados por nombres de archivo. La ordenación es natural, es decir, maneja correctamente los números en los nombres y devuelve, por ejemplo, foo1.txt antes de foo10.txt.

Finder también permite ordenar usando una función personalizada. Esta recibe como parámetro dos objetos Nette\Utils\FileInfo y debe devolver el resultado de la comparación con el operador <=>, es decir, -1, 0 o 1. Por ejemplo, así ordenamos los archivos por tamaño:

$finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize());

Múltiples búsquedas diferentes

Si necesitas encontrar varios archivos diferentes en diferentes ubicaciones o que cumplan otros criterios, usa el método append(). Devuelve un nuevo objeto Finder, por lo que es posible encadenar llamadas a métodos:

($finder = new Finder) // ¡guardamos el primer Finder en la variable $finder!
	->files('*.php')   // en src/ buscamos archivos *.php
	->from('src')
	->append()
	->files('*.md')    // en docs/ buscamos archivos *.md
	->from('docs')
	->append()
	->files('*.json'); // en la carpeta actual buscamos archivos *.json

Alternativamente, se puede usar el método append() para añadir un archivo específico (o un array de archivos). Entonces devuelve el mismo objeto Finder:

$finder = Finder::findFiles('*.txt')
	->append(__FILE__);

FileInfo

Nette\Utils\FileInfo es una clase que representa un archivo o directorio en los resultados de la búsqueda. Es una extensión de la clase SplFileInfo, que proporciona información como el tamaño del archivo, fecha de última modificación, nombre, ruta, etc.

Además, proporciona métodos para devolver la ruta relativa, lo cual es útil durante la navegación en profundidad:

foreach (Finder::findFiles('*.jpg')->from('.') as $file) {
	$absoluteFilePath = $file->getRealPath();
	$relativeFilePath = $file->getRelativePathname();
}

Además, tienes disponibles métodos para leer y escribir el contenido del archivo:

foreach ($finder as $file) {
    $contents = $file->read();
    // ...
    $file->write($contents);
}

Devolución de resultados como array

Como se ha visto en los ejemplos, Finder implementa la interfaz IteratorAggregate, por lo que puedes usar foreach para recorrer los resultados. Está programado de tal manera que los resultados se cargan solo durante el recorrido, por lo que si tienes una gran cantidad de archivos, no se espera a que se lean todos.

También puedes obtener los resultados como un array de objetos Nette\Utils\FileInfo, usando el método collect(). El array no es asociativo, sino numérico.

$array = $finder->findFiles('*.php')->collect();
versión: 4.0