Finder: procurando arquivos
Precisa encontrar arquivos que correspondam a uma determinada máscara? O Finder pode ajudá-lo com isso. É uma ferramenta versátil e rápida para navegar na estrutura de diretórios.
Instalação:
composer require nette/utils
Os exemplos pressupõem a criação de um alias:
use Nette\Utils\Finder;
Uso
Primeiro, vamos mostrar 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 busca é o diretório atual, mas você pode alterá-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 procurar?
Além do método findFiles()
, existem também findDirectories()
, que busca apenas diretórios, e
find()
, que busca ambos. Esses métodos são estáticos, portanto, podem ser chamados sem criar uma instância.
O parâmetro com a máscara é opcional; se você não o fornecer, tudo será pesquisado.
foreach (Finder::find() as $file) {
echo $file; // agora todos os arquivos e diretórios serão listados
}
Usando os métodos files()
e directories()
, você pode especificar o que mais procurar. Os métodos
podem ser chamados repetidamente, e um array de máscaras também 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
(um objeto recém-criado assim
não procura nada) e especificar o que procurar usando files()
e directories()
:
(new Finder)
->directories() // todos os diretórios
->files('*.php'); // mais todos os arquivos PHP
Na máscara, você pode usar caracteres curinga *
, **
,
?
e [...]
. Você pode até especificar diretórios, por exemplo, src/*.php
encontrará
todos os arquivos PHP no diretório src
.
Links simbólicos também são considerados diretórios ou arquivos.
Onde procurar?
O diretório padrão para busca é o diretório atual. Você pode alterá-lo usando os métodos in()
e
from()
. Como os nomes dos métodos sugerem, in()
busca apenas no diretório especificado, enquanto
from()
busca também em seus subdiretórios (recursivamente). Se você quiser buscar recursivamente no diretório
atual, pode usar from('.')
.
Esses métodos podem ser chamados várias vezes ou podem receber vários caminhos como um array; os arquivos serão então
pesquisados em todos os diretórios. Se algum dos diretórios não existir, uma exceção
Nette\UnexpectedValueException
será lançada.
Finder::findFiles('*.php')
->in(['src', 'tests']) // busca diretamente em src/ e tests/
->from('vendor'); // busca também nos subdiretórios de vendor/
Caminhos relativos são relativos ao diretório atual. Claro, caminhos absolutos também podem ser fornecidos:
Finder::findFiles('*.php')
->in('/var/www/html');
É possível usar caracteres curinga *
, **
, ?
no
caminho. Por exemplo, com o caminho src/*/*.php
, você pode procurar todos os arquivos PHP nos diretórios de
segundo nível dentro do diretório src
. O caractere **
, chamado globstar, é um trunfo poderoso, pois
permite buscar também em subdiretórios: com src/**/tests/*.php
, você procura todos os arquivos PHP no diretório
tests
localizado em src
ou em qualquer um de seus subdiretórios.
Por outro lado, os caracteres curinga [...]
não são suportados no caminho, ou seja, não têm significado
especial, para evitar comportamento indesejado caso você procure, por exemplo, in(__DIR__)
e os caracteres
[]
apareçam acidentalmente no caminho.
Ao buscar arquivos e diretórios em profundidade, o diretório pai é retornado primeiro, seguido pelos arquivos contidos
nele. Isso pode ser invertido usando childFirst()
.
Caracteres curinga
Na máscara, você pode usar vários caracteres especiais:
*
– substitui qualquer número de caracteres (exceto/
)**
– substitui qualquer número de caracteres, incluindo/
(ou seja, permite busca multinível)?
– substitui um único caractere qualquer (exceto/
)[a-z]
– substitui um único caractere da lista de caracteres entre colchetes[!a-z]
– substitui um único caractere fora da lista de caracteres entre colchetes
Exemplos de uso:
img/?.png
– arquivos com nomes de uma letra0.png
,1.png
,x.png
, etc.logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log
– logs no formatoYYYY-MM-DD
src/**/tests/*
– arquivos no diretóriosrc/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óriodocs
Exclusão
Usando o método exclude()
, você pode excluir arquivos e diretórios da busca. Você fornece uma máscara que
o arquivo não deve corresponder. Exemplo de busca por arquivos *.txt
, exceto aqueles que contêm a letra
X
no nome:
Finder::findFiles('*.txt')
->exclude('*X*');
Para pular a travessia de subdiretórios, use exclude()
:
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.
Usando size()
, filtramos pelo tamanho do arquivo. Desta forma, encontramos arquivos com tamanho entre 100 e
200 bytes:
Finder::findFiles('*.php')
->size('>=', 100)
->size('<=', 200);
O método date()
filtra pela data da última modificação do arquivo. Os valores podem ser absolutos ou
relativos à data e hora atuais; por exemplo, desta forma encontramos arquivos modificados nas últimas duas 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 que o arquivo seja incluído nos
resultados.
Exemplo: busca por arquivos PHP que contêm a string Nette
(sem diferenciar maiúsculas de minúsculas):
Finder::findFiles('*.php')
->filter(fn($file) => strcasecmp($file->read(), 'Nette') === 0);
Filtragem em profundidade
Ao buscar recursivamente, você pode definir a profundidade máxima de travessia usando o método limitDepth()
.
Se você definir limitDepth(1)
, apenas os primeiros subdiretórios são percorridos; limitDepth(0)
desativa a travessia em profundidade, e um valor de –1 remove o limite.
O Finder permite decidir em qual diretório entrar durante a travessia usando funções personalizadas. 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 ordenar os resultados.
O método sortByName()
ordena os resultados pelos nomes dos arquivos. A ordenação é natural, o que significa
que lida corretamente com números nos nomes e retorna, por exemplo, foo1.txt
antes de foo10.txt
.
O Finder também permite ordenar usando uma função personalizada. Ela recebe dois objetos Nette\Utils\FileInfo
como parâmetros e deve retornar o resultado da comparação usando o operador <=>
, ou seja, -1
,
0
ou 1
. Por exemplo, desta forma ordenamos os arquivos por tamanho:
$finder->sortBy(fn($a, $b) => $a->getSize() <=> $b->getSize());
Múltiplas buscas diferentes
Se você precisa 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
, permitindo encadear chamadas de método:
($finder = new Finder) // armazenamos o primeiro Finder na variável $finder!
->files('*.php') // em src/ procuramos arquivos *.php
->from('src')
->append()
->files('*.md') // em docs/ procuramos arquivos *.md
->from('docs')
->append()
->files('*.json'); // na pasta atual procuramos arquivos *.json
Alternativamente, o método append()
pode ser usado para adicionar um arquivo específico (ou um array de
arquivos). Ele então 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 o caminho relativo, o que é útil ao percorrer em profundidade:
foreach (Finder::findFiles('*.jpg')->from('.') as $file) {
$absoluteFilePath = $file->getRealPath();
$relativeFilePath = $file->getRelativePathname();
}
Você também tem métodos disponíveis para ler e escrever o conteúdo do arquivo:
foreach ($finder as $file) {
$contents = $file->read();
// ...
$file->write($contents);
}
Retornando resultados como um array
Como visto nos exemplos, o Finder implementa a interface IteratorAggregate
, então você pode usar
foreach
para percorrer os resultados. Ele é programado de forma que os resultados sejam carregados apenas durante a
iteração, então, se você tiver um grande número de arquivos, não há espera até que todos sejam lidos.
Você também pode ter os resultados retornados como um array de objetos Nette\Utils\FileInfo
usando o método
collect()
. O array não é associativo, mas numérico.
$array = $finder->findFiles('*.php')->collect();