Vymyslet dobré API je někdy neskutečný porod. Vedle toho skutečný porod je procházka růžovou ordinací. Snad dva roky jsem neustále překopával třídu na procházení adresářů. A stále nebyl spokojen. Přitom taková blbost. Existuje totiž spousta variant toho, co a jak hledat, které soubory vracet, které adresáře vracet a které procházet rekurzivně, nebo naopak kterým se vyhnout. Také jsem potřeboval řešit specifické situace, kupříkladu když během procházení adresářovou strukturou teprve zjišťuji dodatečná pravidla. Otázkou bylo, jak to navrhnout univerzálně a pokud možno srozumitelně.

Výsledkem snažení je třída Nette\Finder, jejíž API není dokonalé, ale je zatím asi to nejpoužitelnější, k jakému jsem se dopracoval. Můžete si ji stáhnout na GitHubu.

Pár příkladů použití:

// nerekurzivní hledání souborů *.txt v adresáři $dir
foreach (Finder::findFiles('*.txt')->in($dir) as $key => $file) {
	echo $key; // $key je řetězec s názvem souboru včetně cesty
	echo $file; // $file je objektem SplFileInfo
}

// rekurzivní hledání souborů *.txt
foreach (Finder::findFiles('*.txt')->from($dir) as $file) {
	echo $file;
}

// hledání podle více masek a dokonce z více adresářů v rámci jedné iterace
foreach (Finder::findFiles('*.txt', '*.php')->in($dir1, $dir2) as $file) {
}

// rekurzivní hledání souborů *.txt obsahujících číslici v názvu
foreach (Finder::findFiles('*[0-9]*.txt')
	->from($dir) as $file) {
}

// rekurzivní hledání souborů *.txt kromě těch, co obsahují v názvu X
// pozn.: exclude se tu vztahuje na findFiles()
foreach (Finder::findFiles('*.txt')->exclude('*X*')
	->from($dir) as $file) {
}

// rekurzivní hledání souborů *.txt umístěných v adresáři
// začínajícím na "te" ale nikoliv "temp"
foreach (Finder::findFiles('te*/*.txt')->exclude('temp*/*')
	->from($dir) as $file) {
}

Omezit hloubku procházení lze metodou limitDepth().

Kromě souborů lze hledat i adresáře přes Finder::findDirectories('subdir*') nebo obojí Finder::find('file.txt'). V takovém případě se maska vztahuje na soubory, nikoliv adresáře.

Adresáře, kterým se chceme zcela vyhnout, uvedeme za klauzulí „from“:

// tady se exclude vztahuje na klauzuli "from"
foreach (Finder::findFiles('*.php')
	->from($dir)->exclude('temp', '.git') as $file) {
}

Nejen maskou lze výsledky filtrovat:

// prochází soubory v rozmezí 100B až 200B
foreach (Finder::findFiles('*.php')->size('>=', 100)->size('<=', 200)
	->from($dir) as $file) {
}

// prochází soubory změněné v posledních dvou týdnech
foreach (Finder::findFiles('*.php')->date('>', '- 2 weeks')
	->from($dir) as $file) {
}

// prochází soubory PHP s počtem řádku větším než 1000 filtrujeme callbackem
$finder = Finder::findFiles('*.php')->filter(function($file) {
	return count(file($file->getPathname())) > 1000;
})->from($dir);

V Nette lze jít dál a třídu Nette\Finder skrze extension methods dále rozšiřovat a poté můžete třeba:

// hledat obrázky s rozměry většími než 50px x 50px
foreach (Finder::findFiles('*')->dimensions('>50', '>50')
	->from($dir) as $file) {
}

Třída funguje na Windows i Linuxu a je napsána co nejoptimálněji, měla by tudíž fungovat velmi rychle a neprochází zbytečně adresáře, které nemá. Enjoy!