Buscador: Búsqueda de archivos

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

Se instala:

composer require nette/utils

Los ejemplos suponen que se ha creado un alias:

use Nette\Utils\Finder;

Utilizando

En primer lugar, vamos a ver cómo se puede utilizar Nette\Utils\Finder para listar los nombres de archivo con las extensiones .txt y .md en el directorio actual:

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

El directorio por defecto 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 muchos métodos útiles. La clave $name contiene la ruta al archivo como una cadena.

¿Qué buscar?

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

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

Utilice los métodos files() y directories() para añadir qué más buscar. Los métodos se pueden llamar repetidamente y se puede proporcionar una matriz de máscaras como parámetro:

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

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

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

Puede utilizar comodines *, **, ? and [...] en la máscara. Incluso puede especificar en directorios, por ejemplo src/*.php buscará todos los archivos PHP en el directorio src.

El directorio de búsqueda por defecto es el directorio actual. Puede cambiarlo utilizando los métodos in() y from(). Como puedes ver por los nombres de los métodos, in() busca sólo en el directorio actual, mientras que from() busca también en sus subdirectorios (recursivamente). Si desea buscar recursivamente en el directorio actual, puede utilizar from('.').

Estos métodos se pueden llamar varias veces o se les pueden pasar varias rutas como matrices, entonces se buscará en todos los directorios. Si uno de los directorios no existe, se lanza un Nette\UnexpectedValueException.

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

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

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

Wildcards comodines *, **, ? can be used in the path. For example, you can use the path src/*/*.php para buscar todos los archivos PHP en los directorios de segundo nivel en el directorio src. El carácter **, llamado globstar, es una poderosa carta de triunfo porque permite buscar también en subdirectorios: use src/**/tests/*.php para buscar todos los archivos PHP en el directorio tests ubicados en src o cualquiera de sus subdirectorios.

Por otro lado, los caracteres comodín [...] no están soportados en la ruta, es decir, no tienen un significado especial para evitar comportamientos no deseados en caso de que busque por ejemplo in(__DIR__) y por casualidad aparezcan caracteres [] en la ruta.

Al buscar archivos y directorios en profundidad, se devuelve primero el directorio padre y luego los archivos que contiene, lo que puede invertirse con childFirst().

Comodines

Puede utilizar varios caracteres especiales en la máscara:

  • * – replaces any number of arbitrary characters (except /)
  • ** – sustituye a cualquier número de caracteres arbitrarios, incluido / (es decir, puede buscarse en varios niveles)
  • ? – replaces one arbitrary character (except /)
  • [a-z] – sustituye un carácter de la lista de caracteres entre corchetes
  • [!a-z] – sustituye un carácter fuera de la lista de caracteres entre corchetes

Ejemplos de utilización:

  • img/?.png – archivos con el 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 – archivos de registro con el formato YYYY-MM-DD
  • src/**/tests/* – ficheros en el directorio src/tests, src/foo/tests, src/foo/bar/tests etc.
  • docs/**.md – todos los archivos con la extensión .md en todos los subdirectorios del directorio docs

Excluyendo

Utilice el método exclude() para excluir archivos y directorios de las búsquedas. Se especifica una máscara con la que el fichero no debe coincidir. Ejemplo de búsqueda de archivos *.txt excepto los que contienen la letra X en el nombre:

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

Utilice exclude() para omitir los subdirectorios buscados:

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

Filtrado

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

Utilice size() para filtrar por tamaño de archivo. De este modo, encontraremos los ficheros con tamaños comprendidos entre 100 y 200 bytes:

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

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

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

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

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

Ejemplo: buscar archivos PHP que contengan la cadena Nette (sin distinguir mayúsculas de minúsculas):

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

Filtrado en profundidad

Al realizar búsquedas recursivas, puedes establecer la profundidad máxima de rastreo utilizando el método limitDepth(). Si establece limitDepth(1), sólo se rastrean los primeros subdirectorios, limitDepth(0) desactiva el rastreo en profundidad y un valor de –1 anula el límite.

El Finder permite utilizar sus propias funciones para decidir en qué directorio se entra al navegar. La función recibe un objeto Nette\Utils\FileInfo como parámetro y debe devolver true para entrar en el directorio:

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

Ordenar

El Buscador también ofrece varias funciones para ordenar los resultados.

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

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

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

Múltiples búsquedas diferentes

Si necesita encontrar varios archivos diferentes en distintas ubicaciones o que cumplan distintos criterios, utilice el método append(). Devuelve un nuevo objeto Finder para que pueda encadenar llamadas a métodos:

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

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

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

FileInfo

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

Además, proporciona métodos para devolver rutas relativas, lo cual es útil cuando se navega en profundidad:

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

También dispone de métodos para leer y escribir el contenido de un fichero:

foreach ($buscador como $archivo) {
    $contenidos = $file->read();
    // ...
    $file->write($contents);
}

Devolución de resultados como array

Como se ha visto en los ejemplos, el Finder implementa la interfaz IteratorAggregate, así que puedes usar foreach para navegar por los resultados. Está programado para que los resultados sólo se carguen mientras navegas, así que si tienes un gran número de archivos, no espera a que se lean todos.

También puede obtener los resultados como una matriz de objetos Nette\Utils\FileInfo, utilizando el método collect(). La matriz no es asociativa, sino numérica.

$array = $finder->findFiles('*.php')->collect();