Finder: wyszukiwanie plików

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

Instalacja:

composer require nette/utils

Przykłady zakładają utworzony alias:

use Nette\Utils\Finder;

Użycie

Najpierw pokażemy, jak za pomocą Nette\Utils\Finder można wypisać nazwy plików z rozszerzeniami .txt i .md w bieżącym katalogu:

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

Domyślnym katalogiem do wyszukiwania jest bieżący katalog, ale można go zmienić za pomocą metod in() lub from(). Zmienna $file jest instancją klasy FileInfo z wieloma przydatnymi metodami. W kluczu $name znajduje się ścieżka do pliku jako ciąg znaków.

Czego szukać?

Oprócz metody findFiles() istnieje również findDirectories(), która szuka tylko katalogów, oraz find(), która szuka obu. Te metody są statyczne, więc można je wywoływać bez tworzenia instancji. Parametr z maską jest opcjonalny, jeśli go nie podasz, wyszukane zostanie wszystko.

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

Za pomocą metod files() i directories() możesz uzupełniać, co jeszcze ma być wyszukiwane. Metody można wywoływać wielokrotnie, a jako parametr można podać również 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 (tak utworzony świeży obiekt niczego nie wyszukuje) i określenie, czego szukać za pomocą files() i directories():

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

W masce można używać symboli wieloznacznych *, **, ? i [...]. Można nawet określić katalogi, na przykład src/*.php wyszuka wszystkie pliki PHP w katalogu src.

Linki symboliczne są również traktowane jako katalogi lub pliki.

Gdzie szukać?

Domyślnym katalogiem do wyszukiwania jest bieżący katalog. Zmienisz go za pomocą metod in() i from(). Jak wynika z nazw metod, in() szuka tylko w danym katalogu, podczas gdy from() szuka również w jego podkatalogach (rekurencyjnie). Jeśli chcesz wyszukiwać rekurencyjnie w bieżącym katalogu, możesz użyć from('.').

Te metody można wywoływać wielokrotnie lub przekazać im wiele ścieżek jako tablicę, pliki będą wtedy szukane we wszystkich katalogach. Jeśli któryś z katalogów nie istnieje, zostanie rzucony wyjątek Nette\UnexpectedValueException.

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

Ścieżki względne są względne względem bieżącego katalogu. Oczywiście można również podać ścieżki absolutne:

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

W ścieżce można używać symboli wieloznacznych symbole wieloznaczne *, **, ?. Możesz na przykład za pomocą ścieżki src/*/*.php szukać wszystkich plików PHP w katalogach drugiego poziomu w katalogu src. Znak ** nazywany globstar jest potężnym atutem, ponieważ umożliwia szukanie również w podkatalogach: za pomocą src/**/tests/*.php szukasz wszystkich plików PHP w katalogu tests znajdującym się w src lub dowolnym jego podkatalogu.

Natomiast symbole wieloznaczne [...] w ścieżce nie są obsługiwane, tj. nie mają specjalnego znaczenia, aby nie dochodziło do niepożądanego zachowania w przypadku, gdy będziesz szukać na przykład in(__DIR__) i przypadkiem w ścieżce pojawią się znaki [].

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

Symbole wieloznaczne

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

  • * – zastępuje dowolną liczbę dowolnych znaków (oprócz /)
  • ** – zastępuje dowolną liczbę dowolnych znaków, w tym / (tj. można szukać wielopoziomowo)
  • ? – zastępuje jeden dowolny znak (oprócz /)
  • [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 użycia:

  • img/?.png – pliki o jednoliterowej nazwie 0.png, 1.png, x.png, itp.
  • logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log – logi 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

Wykluczenie

Za pomocą metody exclude() można wykluczyć pliki i katalogi z wyszukiwania. Podajesz maskę, której plik nie może pasować. Przykład wyszukiwania plików *.txt oprócz tych, które zawierają w nazwie literę X:

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

Aby pominąć przeglądane podkatalogi, użyj exclude():

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

Filtrowanie

Finder oferuje kilka metod filtrowania wyników (tj. ich redukcji). Możesz je łączyć i wywoływać wielokrotnie.

Za pomocą size() filtrujemy według rozmiaru pliku. W ten sposób znajdziemy pliki o rozmiarze w zakresie 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ć absolutne lub względne względem bieżącej daty i czasu, na przykład w ten sposób znajdziemy pliki zmodyfikowane 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 za pomocą własnych funkcji. Funkcja otrzymuje jako parametr obiekt Nette\Utils\FileInfo i musi zwrócić true, aby plik został uwzględniony w wynikach.

Przykład: wyszukiwanie plików PHP, które zawierają ciąg znaków Nette (bez względu na wielkość liter):

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

Filtrowanie w głąb

Podczas wyszukiwania rekurencyjnego możesz ustawić maksymalną głębokość przeglądania za pomocą metody limitDepth(). Jeśli ustawisz limitDepth(1), przeglądane są tylko pierwsze podkatalogi, limitDepth(0) wyłącza przeglądanie w głąb, a wartość –1 znosi limit.

Finder umożliwia za pomocą własnych funkcji decydowanie, 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 do sortowania wyników.

Metoda sortByName() sortuje wyniki według nazw plików. Sortowanie jest naturalne, czyli poprawnie radzi sobie z liczbami w nazwach i zwraca np. foo1.txt przed foo10.txt.

Finder umożliwia również sortowanie za pomocą własnej funkcji. Otrzymuje ona jako parametr dwa obiekty Nette\Utils\FileInfo i musi zwrócić wynik porównania operatorem <=>, czyli -1, 0 lub 1. Na przykład w ten sposób posortujemy pliki według rozmiaru:

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

Wiele różnych wyszukiwań

Jeśli potrzebujesz znaleźć więcej różnych plików w różnych lokalizacjach lub spełniających inne kryteria, użyj metody append(). Zwraca ona nowy obiekt Finder, więc możliwe jest łączenie wywołań metod w łańcuch:

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

Alternatywnie można użyć metody append() do dodania konkretnego pliku (lub tablicy plików). Wtedy 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óra dostarcza informacji takich jak rozmiar pliku, data ostatniej modyfikacji, nazwa, ścieżka itp.

Dodatkowo dostarcza metody do zwracania ścieżki względnej, co jest przydatne podczas przeglądania w głąb:

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

Ponadto masz do dyspozycji metody do odczytu i zapisu zawartości pliku:

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

Zwracanie wyników jako tablica

Jak było widać na 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 się, aż wszystkie zostaną odczytane.

Wyniki można również otrzymać jako tablicę obiektów Nette\Utils\FileInfo, za pomocą metody collect(). Tablica nie jest asocjacyjna, lecz numeryczna.

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