Finder: wyszukiwanie plików

Potrzebujesz znaleźć pliki pasujące do określonej maski? Finder może ci pomóc. Jest to wszechstronne i szybkie narzędzie do przeglądania struktury katalogów.

Instalacja:

composer require nette/utils

W przykładach założono, że alias został utworzony:

use Nette\Utils\Finder;

Korzystanie z

Najpierw zobaczmy, jak można użyć Nette\Utils\Finder do wypisania nazw plików z rozszerzeniami .txt i .md w bieżącym katalogu:

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

Domyślnym katalogiem dla wyszukiwania jest katalog bieżący, ale możesz go zmienić używając metod in() lub from(). Zmienna $file jest instancją klasy FileInfo z wieloma przydatnymi metodami. Klucz $name zawiera ścieżkę do pliku jako ciąg znaków.

Na co zwrócić uwagę?

Oprócz metody findFiles() istnieje również findDirectories(), która przeszukuje tylko katalogi, oraz find(), która przeszukuje oba katalogi. Te metody są statyczne, więc można je wywołać bez tworzenia instancji. Parametr maski jest opcjonalny, jeśli go nie określisz, wszystko jest przeszukiwane.

foreach (Finder::find() as $file) {
	echo $file; // wszystkie pliki i katalogi są teraz wymienione
}

Użyj metod files() i directories(), aby dodać, co jeszcze należy wyszukać. Metody te mogą być wywoływane wielokrotnie, a jako parametr można podać tablicę masek:

Finder::findDirectories('vendor') // wszystkie katalogi
	->files(['*.php', '*.phpt']); // plus wszystkie pliki PHP

Alternatywą dla metod statycznych jest utworzenie instancji za pomocą new Finder (świeży obiekt utworzony w ten sposób niczego nie wyszukuje) i określenie, czego ma szukać za pomocą files() i directories():

(new Finder)
	->directories() // wszystkie katalogi
	->files('*.php'); // plus wszystkie pliki PHP

W masce można używać znaków wieloznacznych *, **, ? a [...]. Możesz nawet określić w katalogach, na przykład src/*.php będzie szukać wszystkich plików PHP w katalogu src.

Symlinki są również uważane za katalogi lub pliki.

Gdzie szukać?

Domyślnym katalogiem wyszukiwania jest katalog bieżący. Możesz to zmienić używając metod in() i from() Jak widać z nazw metod, in() przeszukuje tylko bieżący katalog, natomiast from() przeszukuje jego podkatalogi (rekurencyjnie). Jeśli chcesz szukać rekurencyjnie w bieżącym katalogu, możesz użyć from('.').

Możesz wywołać te metody wielokrotnie lub przekazać do nich wiele ścieżek jako tablice, wtedy pliki będą przeszukiwane we wszystkich katalogach. Jeśli jeden z katalogów nie istnieje, rzucany jest wyjątek Nette\UnexpectedValueException.

Finder::findFiles('*.php')
	->in(['src', 'tests']) // wyszukuje bezpośrednio w src/ i tests/
	->from('vendor'); // wyszukuje również w podkatalogach vendor/

Ścieżki relatywne są względne w stosunku do bieżącego katalogu. Oczywiście można również określić ścieżki bezwzględne:

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

Możesz użyć symboli wieloznacznych *, **, ?. Můžete tak třeba pomocí cesty src/*/*.php w ścieżce, aby wyszukać wszystkie pliki PHP w katalogach drugiego poziomu w katalogu src. Symbol wieloznaczny **, zwany globstar, jest potężnym atutem, ponieważ pozwala na wyszukiwanie również w podkatalogach: użyj src/**/tests/*.php, aby wyszukać wszystkie pliki PHP w katalogu tests znajdujące się w src lub dowolnym jego podkatalogu.

Z drugiej strony, symbole wieloznaczne [...] znaki nie są obsługiwane w ścieżce, tzn. nie mają specjalnego znaczenia, aby uniknąć niepożądanego zachowania w przypadku, gdy użytkownik szuka in(__DIR__) i przypadkowo w ścieżce pojawiają się znaki [].

Podczas wyszukiwania plików i katalogów w głąb, najpierw zwracany jest katalog nadrzędny, a następnie pliki w nim zawarte, co można odwrócić za pomocą childFirst().

Żbiki

W masce można użyć kilku znaków specjalnych:

  • * – nahrazuje libovolný počet libovolných znaků (kromě /)
  • ** – zastępuje dowolną liczbę dowolnych znaków, w tym / (czyli może być przeszukiwany wielopoziomowo)
  • ? – nahrazuje jeden libovolný znak (kromě /)
  • [a-z] – zastępuje jeden znak z listy znaków w nawiasach kwadratowych
  • [!a-z] – zastępuje jeden znak spoza listy znaków w nawiasach kwadratowych

Przykłady zastosowania:

  • img/?.png – pliki o jednoliterowej nazwie 0.png, 1.png, x.png, itd.
  • logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log – pliki dziennika w formacie YYYY-MM-DD
  • src/**/tests/* – pliki w katalogu src/tests, src/foo/tests, src/foo/bar/tests i tak dalej.
  • docs/**.md – wszystkie pliki z rozszerzeniem .md we wszystkich podkatalogach katalogu docs

Z wyłączeniem

Użyj metody exclude(), aby wykluczyć pliki i katalogi z wyszukiwań. Określasz maskę, do której plik nie może pasować. Przykład wyszukiwania plików *.txt z wyjątkiem tych zawierających w nazwie literę X:

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

Użyj exclude() aby pominąć przeglądane podkatalogi:

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

Filtrowanie

Finder oferuje kilka metod filtrowania wyników (czyli ich zmniejszania). Można je łączyć i nazywać wielokrotnie.

Użyj size(), aby filtrować według rozmiaru pliku. W ten sposób znajdujemy pliki o rozmiarach od 100 do 200 bajtów:

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

Metoda date() filtruje według daty ostatniej modyfikacji pliku. Wartości mogą być bezwzględne lub względne w stosunku do bieżącej daty i czasu, na przykład w ten sposób można znaleźć pliki zmienione w ciągu ostatnich dwóch tygodni:

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

Obie funkcje rozumieją operatory >, >=, <, <=, =, !=, <>.

Finder umożliwia również filtrowanie wyników przy użyciu funkcji niestandardowych. Funkcja otrzymuje jako parametr obiekt Nette\Utils\FileInfo i musi zwrócić true, aby włączyć plik do wyników.

Przykład: wyszukaj pliki PHP, które zawierają ciąg Nette (wielkość liter nie ma znaczenia):

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

Filtrowanie głębokości

Podczas wyszukiwania rekurencyjnego można ustawić maksymalną głębokość indeksowania za pomocą metody limitDepth(). Jeśli ustawisz limitDepth(1), indeksowane są tylko pierwsze podkatalogi, limitDepth(0) wyłącza indeksowanie głębokości, a wartość –1 anuluje limit.

Finder pozwala na wykorzystanie własnych funkcji do decydowania, do którego katalogu wejść podczas przeglądania. Funkcja otrzymuje jako parametr obiekt Nette\Utils\FileInfo i musi zwrócić true, aby wejść do katalogu:

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

Sortowanie

Finder oferuje również kilka funkcji sortowania wyników.

Metoda sortByName() sortuje wyniki według nazwy pliku. Sortowanie jest naturalne, tzn. poprawnie obsługuje liczby w nazwach i zwraca np. foo1.txt przed foo10.txt.

Finder umożliwia również sortowanie przy użyciu funkcji niestandardowej. Przyjmuje dwa obiekty Nette\Utils\FileInfo jako parametry i musi zwrócić wynik porównania za pomocą operatora <=>, czyli -1, 0 nebo 1. Na przykład w ten sposób sortujemy pliki według wielkości:

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

Wiele różnych wyszukiwań

Jeśli musisz znaleźć wiele różnych plików w różnych lokalizacjach lub spełniających różne kryteria, użyj metody append(). Zwraca ona nowy obiekt Finder, dzięki czemu możesz łączyć wywołania metod:

($finder = new Finder) // przechowuj pierwszego Findera w zmiennej $finder!
	->files('*.php') // szukaj plików *.php w src/
	->from('src')
	->append()
	->files('*.md') // w docs/ szukaj plików *.md
	->from('docs')
	->append()
	->files('*.json'); // w bieżącym folderze szukaj plików *.json

Alternatywnie możesz użyć metody append(), aby dodać określony plik (lub tablicę plików). Następnie zwraca ten sam obiekt Finder:

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

FileInfo

Nette\Utils\FileInfo to klasa reprezentująca plik lub katalog w wynikach wyszukiwania. Jest to rozszerzenie klasy SplFileInfo, które dostarcza informacji takich jak rozmiar pliku, data ostatniej modyfikacji, nazwa, ścieżka itp.

Dodatkowo zapewnia metody zwracania ścieżek względnych, co jest przydatne podczas głębokiego przeglądania:

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

Masz również metody odczytu i zapisu zawartości pliku:

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

Zwracanie wyników w postaci tablicy

Jak widać w przykładach, Finder implementuje interfejs IteratorAggregate, więc możesz użyć foreach do przeglądania wyników. Jest zaprogramowany tak, że wyniki są ładowane tylko w trakcie przeglądania, więc jeśli masz dużą liczbę plików, nie czeka na odczytanie ich wszystkich.

Możesz również zlecić zwrócenie wyników w postaci tablicy obiektów Nette\Utils\FileInfo, używając metody collect() Tablica nie jest asocjacyjna, lecz numeryczna.

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