Finder: vyhledávání souborů

Potřebujete najít soubory vyhovující určité masce? Finder vám v tom pomůže. Je to všestranný a rychlý nástroj pro procházení adresářové struktury.

Instalace:

composer require nette/utils

Příklady předpokládají vytvořený alias:

use Nette\Utils\Finder;

Použití

Nejprve si ukážeme, jak můžete pomocí Nette\Utils\Finder vypsat jména souborů s příponami .txt a .md v aktuálním adresáři:

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

Výchozí adresář pro hledání je aktuální adresář, ale můžete ho změnit pomocí metod in() nebo from(). Proměnná $file je instancí třídy FileInfo se spoustou užitečných metod. V klíči $name je cesta k souboru jako řetězec.

Co se má hledat?

Kromě metody findFiles() existuje i findDirectories(), která hledá jen adresáře, a find(), která hledá obojí. Tyto metody jsou statické, takže je lze volat bez vytvoření instance. Parameter s maskou je volitelný, pokud ho neuvedete, vyhledá se vše.

foreach (Finder::find() as $file) {
	echo $file; // nyní se vypíší všechny soubory i adresáře
}

Pomocí metod files() a directories() můžete doplňovat co dalšího se má vyhledat. Metody lze volat opakovaně a jako parametr lze uvést i pole masek:

Finder::findDirectories('vendor') // všechny adresáře
	->files(['*.php', '*.phpt']); // plus všechny PHP soubory

Alternativou statických metod je vytvoření instance pomocí new Finder (takto vytvořený čerstvý objekt nevyhledává nic) a uvedení co hledat pomocí files() a directories():

(new Finder)
	->directories()      // všechny adresáře
	->files('*.php');    // plus všechny PHP soubory

V masce můžete používat zástupné znaky *, **, ? a [...]. Dokonce můžete specifikovat i v adresáře, například src/*.php vyhledá všechny PHP soubory v adresáři src.

Kde se má hledat?

Výchozí adresář pro hledání je aktuální adresář. Změníte ho pomocí metod in() a from(). Jak je z názvů metod patrné, in() hledá pouze v daném adresáři, zatímco from() hledá i v jeho podadresářích (rekurzivně). Pokud chcete vyhledávat rekurzivně v aktuálním adresáři, můžete použít from('.').

Tyto metody lze volat vícekrát nebo jim předat více cest jakožto pole, soubory se pak budou hledat ve všech adresářích. Pokud některý z adresářů neexistuje, vyhodí se výjimka Nette\UnexpectedValueException.

Finder::findFiles('*.php')
	->in(['src', 'tests']) // hledá přímo v src/ a tests/
	->from('vendor');      // hledá i v podadresářích vendor/

Relativní cesty jsou relativní vůči aktuálnímu adresáři. Lze samozřejme uvést i absolutní cesty:

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

V cestě je možné použít zástupné znaky zástupné znaky *, **, ?. Můžete tak třeba pomocí cesty src/*/*.php hledat všechny PHP soubory v adresářích druhé úrovně v adresáři src. Znak ** nazývaný globstar je mocným trumfem, protože umožňuje hledat i v podadresářích: pomocí src/**/tests/*.php hledáte všechny PHP soubory v adresáři tests nacházejícím se v src nebo jakémkoliv jeho podadresáři.

Naopak zástupné znaky [...] v cestě podporovány nejsou, tj. nemají speciální význam, aby nedocházelo k nežádoucímu chování v případě, že budete hledat třeba in(__DIR__) a náhodou v cestě se budou vyskytovat znaky [].

Při vyhledávání souborů i adresáře do hloubky se vrací nejprve rodičovský adresář a teprve poté soubory v něm obsažené, což lze obrátit pomocí childFirst().

Zástupné znaky

V masce můžete používat několik speciálních znaků:

  • * – nahrazuje libovolný počet libovolných znaků (kromě /)
  • ** – nahrazuje libovolný počet libovolných znaků včetně / (tj. lze hledat víceúrovňově)
  • ? – nahrazuje jeden libovolný znak (kromě /)
  • [a-z] – nahrazuje jeden znak ze seznamu znaků v hranatých závorkách
  • [!a-z] – nahrazuje jeden znak mimo seznam znaků v hranatých závorkách

Příklady použití:

  • img/?.png – soubory s jednopísmenným názvem 0.png, 1.png, x.png, atd.
  • logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log – logy ve formátu YYYY-MM-DD
  • src/**/tests/* – soubory v adresáři src/tests, src/foo/tests, src/foo/bar/tests a tak dále.
  • docs/**.md – všechny soubory s příponou .md ve všech podadresářích adresáře docs

Vyloučení

Pomocí metody exclude() lze vyloučít soubory a adresáře z vyhledávání. Uvedete masku, které soubor nesmí vyhovovat. Příklad hledání souborů *.txt kromě těch, co obsahují v názvu písmeno X:

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

Vynechání procházených podadresářů použijte exclude():

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

Filtrování

Finder nabízí několik metod pro filtrování výsledků (tj. jejich redukci). Můžete je kombinovat a volat opakovaně.

Pomocí size() filtrujeme podle velikosti souboru. Takto najdeme soubory s velikostí v rozmezí 100 až 200 bytů:

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

Metoda date() filtruje podle data poslední změny souboru. Hodnoty mohou být absolutní nebo relativní k aktuálnímu datu a času, například takto najdeme soubory změněné v posledních dvou týdnech:

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

Obě funkce rozumí operátorům >, >=, <, <=, =, !=, <>.

Finder umožňuje také filtrovat výsledky pomocí vlastních funkcí. Funkce dostane jako parametr objekt Nette\Utils\FileInfo a musí vrátit true, aby byl soubor zahrnut do výsledků.

Příklad: hledání souborů PHP, které obsahují řetězec Nette (bez ohledu na velikost písmen):

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

Filtrování do hloubky

Při rekurzivním vyhledávání můžete nastavit maximální hloubku procházení pomocí metody limitDepth(). Pokud nastavíte limitDepth(1), prochází se pouze první podadresáře, limitDepth(0) vypne procházení do hloubky a hodnota –1 ruší limit.

Finder umožňuje pomocí vlastních funkcí rozhodovat, do kterého adresáře se má při procházení vstupit. Funkce dostane jako parametr objekt Nette\Utils\FileInfo a musí vrátit true, aby se do adresáře vstoupilo:

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

Řazení

Finder nabízí také několik funkcí pro řazení výsledků.

Metoda sortByName() řadí výsledky podle názvů souborů. Řazení je naturální, tedy správně si poradí s čísly v názvech a vrací např. foo1.txt před foo10.txt.

Finder umožňuje také řadit pomocí vlastní funkce. Ta dostane jako parametr dva objekty Nette\Utils\FileInfo a musí vrátit výsledek porovnání operátorem <=>, tedy -1, 0 nebo 1. Například takto seřadíme soubory podle velikosti:

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

Více různých hledání

Pokud potřebujete najít více různých souborů v různých lokacích nebo splňujích jiná kritéria, použijte metodu append(). Vrací nový objekt Finder, takže je možné řetězit volání metod:

($finder = new Finder) // do proměnné $finder si uložíme první Finder!
	->files('*.php')   // v src/ hledáme soubory *.php
	->from('src')
	->append()
	->files('*.md')    // v docs/ hledáme soubory *.md
	->from('docs')
	->append()
	->files('*.json'); // v aktuální složce hledáme soubory *.json

Alternativně lze použít metodu append() pro přidání konkrétního souboru (nebo pole souborů). Pak vrací ten samý objekt Finder:

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

FileInfo

Nette\Utils\FileInfo je třída reprezentující soubor nebo adresář ve výsledků hledání. Jde o rozšíření třídy SplFileInfo, která poskytuje informace, jako je velikost souboru, datum poslední změny, jméno, cesta, atd.

Navic poskytuje metody pro vrácení relativní cesty, což je užitečné při procházení do hloubky:

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

Dále máte k dispozici metody pro přečtení a zápis obsahu souboru:

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

Vrácení výsledků jako pole

Jak bylo vidět v příkladech, Finder implementuje rozhraní IteratorAggregate, takže můžete použít foreach pro procházení výsledků. Je naprogramovaný tak, že výsledky jsou načítány pouze v průběhu procházení, takže pokud máte velké množství souborů, nečeká se, než se všechny přečtou.

Výsledky si můžete nechat také vrátit jako pole objektů Nette\Utils\FileInfo, a to metodou collect(). Pole není asociativní, ale numerické.

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