Finder: Busca de arquivos

Precisa encontrar arquivos que combinem com uma determinada máscara? O Finder pode lhe ajudar. É uma ferramenta versátil e rápida para navegar na estrutura do diretório.

Instalação:

composer require nette/utils

Os exemplos assumem que um pseudônimo foi criado:

use Nette\Utils\Finder;

Usando

Primeiro, vamos ver como você pode usar Nette\Utils\Finder para listar os nomes dos arquivos com as extensões .txt e .md no diretório atual:

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

O diretório padrão para a busca é o diretório atual, mas você pode mudá-lo usando os métodos in() ou from(). A variável $file é uma instância da classe FileInfo com muitos métodos úteis. A chave $name contém o caminho para o arquivo como uma string.

O que pesquisar?

Além do método findFiles(), há também findDirectories(), que busca somente diretórios, e find(), que busca ambos. Estes métodos são estáticos, portanto, podem ser chamados sem criar uma instância. O parâmetro da máscara é opcional, se você não especificá-lo, tudo é pesquisado.

foreach (Finder::find() as $file) {
	echo $file; // agora todos os arquivos e diretórios estão listados
}

Use os métodos files() e directories() para adicionar o que mais procurar. Os métodos podem ser chamados repetidamente e um conjunto de máscaras pode ser fornecido como parâmetro:

Finder::findDirectories('vendor') // todos os diretórios
	->files(['*.php', '*.phpt']); // mais todos os arquivos PHP

Uma alternativa aos métodos estáticos é criar uma instância usando new Finder (o objeto fresco criado desta forma não procura por nada) e especificar o que procurar usando files() e directories():

(new Finder)
	->directories()      // todos os diretórios
	->files('*.php');    // mais todos os arquivos PHP

Você pode usar wildcards *, **, ? and [...] na máscara. Você pode até mesmo especificar em diretórios, por exemplo src/*.php procurará por todos os arquivos PHP no diretório src.

Os links simbólicos também são considerados diretórios ou arquivos.

O diretório padrão de busca é o diretório atual. Você pode mudar isto usando os métodos in() e from(). Como você pode ver pelos nomes dos métodos, in() pesquisa somente o diretório atual, enquanto from() pesquisa também seus subdiretórios (recursivamente). Se você quiser pesquisar recursivamente no diretório atual, você pode usar from('.').

Estes métodos podem ser chamados várias vezes ou você pode passar vários caminhos para eles como matrizes, então os arquivos serão pesquisados em todos os diretórios. Se um dos diretórios não existir, um Nette\UnexpectedValueException é lançado.

Finder::findFiles('*.php')
	->in(['src', 'tests']) // pesquisa diretamente em src/ e testes/
	->from('vendor'); // pesquisas também em vendor/ subdiretórios

Os caminhos relativos são relativos ao diretório atual. Naturalmente, caminhos absolutos também podem ser especificados:

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

Wildcards wildcards *, **, ? can be used in the path. For example, you can use the path src/*/*.php para procurar por todos os arquivos PHP nos diretórios de segundo nível no diretório src. O personagem **, chamado globstar, é um poderoso trunfo porque permite que você procure também nos subdiretórios: use src/**/tests/*.php para procurar todos os arquivos PHP no diretório tests localizado em src ou em qualquer um de seus subdiretórios.

Por outro lado, os wildcards [...] Os caracteres não são suportados no caminho, ou seja, eles não têm nenhum significado especial para evitar comportamentos indesejados caso você procure por exemplo in(__DIR__) e por acaso [] caracteres aparecem no caminho.

Ao pesquisar arquivos e diretórios em profundidade, o diretório pai é retornado primeiro e depois os arquivos contidos nele, que podem ser revertidos com childFirst().

Wildcards

Você pode usar vários caracteres especiais na máscara:

  • * – replaces any number of arbitrary characters (except /)
  • ** – substitui qualquer número de caracteres arbitrários, incluindo / (ou seja, pode ser pesquisado em vários níveis)
  • ? – replaces one arbitrary character (except /)
  • [a-z] – substitui um caractere da lista de caracteres entre parênteses rectos
  • [!a-z] – substitui um caractere fora da lista de caracteres entre parênteses rectos

Exemplos de uso:

  • img/?.png – arquivos com o nome de uma única 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 – arquivos de log no formato YYYY-MM-DD
  • src/**/tests/* – arquivos no diretório src/tests, src/foo/tests, src/foo/bar/tests e assim por diante.
  • docs/**.md – todos os arquivos com a extensão .md em todos os subdiretórios do diretório docs

Excluindo

Use o método exclude() para excluir arquivos e diretórios das buscas. Você especifica uma máscara que o arquivo não deve corresponder. Exemplo de busca de arquivos *.txt, exceto aqueles que contêm a letra X no nome:

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

Use exclude() para pular os subdiretórios navegados:

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

Filtragem

O Finder oferece vários métodos para filtrar os resultados (ou seja, reduzi-los). Você pode combiná-los e chamá-los repetidamente.

Use size() para filtrar por tamanho de arquivo. Desta forma, encontramos arquivos com tamanhos entre 100 e 200 bytes:

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

O método date() filtra até a data em que o arquivo foi modificado pela última vez. Os valores podem ser absolutos ou relativos à data e hora atual, por exemplo, é assim que se encontram os arquivos modificados nas duas últimas semanas:

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

Ambas as funções entendem os operadores >, >=, <, <=, =, !=, <>.

O Finder também permite filtrar resultados usando funções personalizadas. A função recebe um objeto Nette\Utils\FileInfo como parâmetro e deve retornar true para incluir o arquivo nos resultados.

Exemplo: procure por arquivos PHP que contenham a seqüência Nette (não sensível a maiúsculas e minúsculas):

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

Filtragem de profundidade

Ao realizar buscas recorrentes, você pode definir a profundidade máxima de rastejamento usando o método limitDepth(). Se você definir limitDepth(1), somente os primeiros subdiretórios são rastreados, limitDepth(0) desativa a profundidade de rastreamento, e um valor de –1 cancela o limite.

O Finder permite que você use suas próprias funções para decidir qual diretório entrar quando estiver navegando. A função recebe um objeto Nette\Utils\FileInfo como parâmetro e deve retornar true para entrar no diretório:

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

Ordenação

O Finder também oferece várias funções para classificar os resultados.

O método sortByName() ordena os resultados por nome de arquivo. A ordenação é natural, ou seja, trata corretamente os números nos nomes e retorna, por exemplo, foo1.txt antes de foo10.txt.

O Finder também permite que você faça a classificação usando uma função personalizada. Ele toma dois objetos Nette\Utils\FileInfo como parâmetros e deve retornar o resultado da comparação com o operador <=>ou seja, -1, 0 nebo 1. Por exemplo, é assim que classificamos os arquivos por tamanho:

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

Múltiplas buscas diferentes

Se você precisar encontrar vários arquivos diferentes em locais diferentes ou que atendam a critérios diferentes, use o método append(). Ele retorna um novo objeto Finder para que você possa fazer chamadas em cadeia pelo método :

($finder = new Finder) // armazenar o primeiro Finder na variável $finder!
	->files('*.php') // procure por arquivos *.php em src/
	->from('src')
	->append()
	->files('*.md')    // em docs/ procurar por arquivos *.md
	->from('docs')
	->append()
	->files('*.json'); // na pasta atual procurar por arquivos *.json

Alternativamente, você pode usar o método append() para adicionar um arquivo específico (ou um conjunto de arquivos). Em seguida, ele retorna o mesmo objeto Finder:

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

FileInfo

Nette\Utils\FileInfo é uma classe que representa um arquivo ou diretório nos resultados da busca. É uma extensão da classe SplFileInfo que fornece informações como tamanho do arquivo, data da última modificação, nome, caminho, etc.

Além disso, fornece métodos para retornar caminhos relativos, o que é útil quando se navega em profundidade:

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

Você também tem métodos para ler e escrever o conteúdo de um arquivo:

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

Retorno de Resultados como um Array

Como visto nos exemplos, o Finder implementa a interface IteratorAggregate, assim você pode usar foreach para pesquisar os resultados. Ele é programado para que os resultados sejam carregados apenas enquanto você navega, assim, se você tiver um grande número de arquivos, ele não espera que todos eles sejam lidos.

Você também pode ter os resultados retornados como um conjunto de Nette\Utils\FileInfo objetos, usando o método collect(). A matriz não é associativa, mas numérica.

$array = $finder->findFiles('*.php')->collect();
versão: 4.0