Finder: Dateisuche

Müssen Sie Dateien finden, die einer bestimmten Maske entsprechen? Der Finder hilft Ihnen dabei. Es ist ein vielseitiges und schnelles Werkzeug zum Durchsuchen der Verzeichnisstruktur.

Installation:

composer require nette/utils

Die Beispiele setzen voraus, dass ein Alias erstellt wurde:

use Nette\Utils\Finder;

Verwendung

Zuerst zeigen wir, wie Sie mit Nette\Utils\Finder Dateinamen mit den Erweiterungen .txt und .md im aktuellen Verzeichnis auflisten können:

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

Das Standardverzeichnis für die Suche ist das aktuelle Verzeichnis, aber Sie können es mit den Methoden in() oder from() ändern. Die Variable $file ist eine Instanz der Klasse FileInfo mit vielen nützlichen Methoden. Im Schlüssel $name befindet sich der Pfad zur Datei als String.

Was soll gesucht werden?

Neben der Methode findFiles() gibt es auch findDirectories(), die nur Verzeichnisse sucht, und find(), die beides sucht. Diese Methoden sind statisch, sodass sie ohne Erstellung einer Instanz aufgerufen werden können. Der Parameter mit der Maske ist optional, wenn Sie ihn nicht angeben, wird alles gesucht.

foreach (Finder::find() as $file) {
	echo $file; // jetzt werden alle Dateien und Verzeichnisse ausgegeben
}

Mit den Methoden files() und directories() können Sie ergänzen, was zusätzlich gesucht werden soll. Die Methoden können wiederholt aufgerufen werden, und als Parameter kann auch ein Array von Masken angegeben werden:

Finder::findDirectories('vendor') // alle Verzeichnisse
	->files(['*.php', '*.phpt']); // plus alle PHP-Dateien

Eine Alternative zu statischen Methoden ist die Erstellung einer Instanz mit new Finder (ein so erstelltes frisches Objekt sucht nichts) und die Angabe, was gesucht werden soll, mit files() und directories():

(new Finder)
	->directories()      // alle Verzeichnisse
	->files('*.php');    // plus alle PHP-Dateien

In der Maske können Sie die Platzhalter *, **, ? und [...] verwenden. Sie können sogar Verzeichnisse angeben, zum Beispiel sucht src/*.php alle PHP-Dateien im Verzeichnis src.

Symlinks werden ebenfalls als Verzeichnisse oder Dateien betrachtet.

Wo soll gesucht werden?

Das Standardverzeichnis für die Suche ist das aktuelle Verzeichnis. Sie ändern es mit den Methoden in() und from(). Wie aus den Methodennamen ersichtlich ist, sucht in() nur im angegebenen Verzeichnis, während from() auch in dessen Unterverzeichnissen (rekursiv) sucht. Wenn Sie rekursiv im aktuellen Verzeichnis suchen möchten, können Sie from('.') verwenden.

Diese Methoden können mehrmals aufgerufen werden oder ihnen mehrere Pfade als Array übergeben werden, die Dateien werden dann in allen Verzeichnissen gesucht. Wenn eines der Verzeichnisse nicht existiert, wird eine Ausnahme Nette\UnexpectedValueException geworfen.

Finder::findFiles('*.php')
	->in(['src', 'tests']) // sucht direkt in src/ und tests/
	->from('vendor');      // sucht auch in Unterverzeichnissen von vendor/

Relative Pfade sind relativ zum aktuellen Verzeichnis. Es können natürlich auch absolute Pfade angegeben werden:

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

Im Pfad können die Platzhalter *, **, ? verwendet werden. Sie können beispielsweise mit dem Pfad src/*/*.php alle PHP-Dateien in Verzeichnissen der zweiten Ebene im Verzeichnis src suchen. Das Zeichen **, genannt Globstar, ist ein mächtiger Trumpf, da es die Suche auch in Unterverzeichnissen ermöglicht: Mit src/**/tests/*.php suchen Sie alle PHP-Dateien im Verzeichnis tests, das sich in src oder einem seiner Unterverzeichnisse befindet.

Im Gegensatz dazu werden die Platzhalter [...] im Pfad nicht unterstützt, d.h. sie haben keine besondere Bedeutung, um unerwünschtes Verhalten zu vermeiden, falls Sie beispielsweise in(__DIR__) suchen und zufällig im Pfad die Zeichen [] vorkommen.

Beim Suchen von Dateien und Verzeichnissen in der Tiefe wird zuerst das übergeordnete Verzeichnis zurückgegeben und erst danach die darin enthaltenen Dateien, was mit childFirst() umgekehrt werden kann.

Platzhalter

In der Maske können Sie mehrere Sonderzeichen verwenden:

  • * – ersetzt eine beliebige Anzahl beliebiger Zeichen (außer /)
  • ** – ersetzt eine beliebige Anzahl beliebiger Zeichen einschließlich / (d.h. es kann mehrstufig gesucht werden)
  • ? – ersetzt ein beliebiges Zeichen (außer /)
  • [a-z] – ersetzt ein Zeichen aus der Liste der Zeichen in eckigen Klammern
  • [!a-z] – ersetzt ein Zeichen außerhalb der Liste der Zeichen in eckigen Klammern

Anwendungsbeispiele:

  • img/?.png – Dateien mit einbuchstabigem Namen 0.png, 1.png, x.png, usw.
  • logs/[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9].log – Logs im Format YYYY-MM-DD
  • src/**/tests/* – Dateien im Verzeichnis src/tests, src/foo/tests, src/foo/bar/tests und so weiter.
  • docs/**.md – alle Dateien mit der Erweiterung .md in allen Unterverzeichnissen des Verzeichnisses docs

Ausschluss

Mit der Methode exclude() können Dateien und Verzeichnisse von der Suche ausgeschlossen werden. Sie geben eine Maske an, der die Datei nicht entsprechen darf. Beispiel für die Suche nach *.txt-Dateien außer denen, die den Buchstaben X im Namen enthalten:

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

Verwenden Sie exclude(), um das Durchsuchen von Unterverzeichnissen zu überspringen:

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

Filterung

Der Finder bietet mehrere Methoden zum Filtern der Ergebnisse (d.h. zu ihrer Reduzierung). Sie können sie kombinieren und wiederholt aufrufen.

Mit size() filtern wir nach Dateigröße. So finden wir Dateien mit einer Größe zwischen 100 und 200 Bytes:

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

Die Methode date() filtert nach dem Datum der letzten Änderung der Datei. Die Werte können absolut oder relativ zum aktuellen Datum und zur aktuellen Uhrzeit sein, zum Beispiel finden wir so Dateien, die in den letzten zwei Wochen geändert wurden:

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

Beide Funktionen verstehen die Operatoren >, >=, <, <=, =, !=, <>.

Der Finder ermöglicht auch das Filtern der Ergebnisse mithilfe eigener Funktionen. Die Funktion erhält als Parameter das Objekt Nette\Utils\FileInfo und muss true zurückgeben, damit die Datei in die Ergebnisse aufgenommen wird.

Beispiel: Suche nach PHP-Dateien, die den String Nette enthalten (unabhängig von der Groß-/Kleinschreibung):

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

Tiefenfilterung

Bei der rekursiven Suche können Sie die maximale Tiefe des Durchlaufs mit der Methode limitDepth() festlegen. Wenn Sie limitDepth(1) setzen, werden nur die ersten Unterverzeichnisse durchlaufen, limitDepth(0) deaktiviert den Tiefendurchlauf und der Wert –1 hebt das Limit auf.

Der Finder ermöglicht es, mithilfe eigener Funktionen zu entscheiden, in welches Verzeichnis beim Durchlaufen eingetreten werden soll. Die Funktion erhält als Parameter das Objekt Nette\Utils\FileInfo und muss true zurückgeben, damit in das Verzeichnis eingetreten wird:

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

Sortierung

Der Finder bietet auch mehrere Funktionen zum Sortieren der Ergebnisse.

Die Methode sortByName() sortiert die Ergebnisse nach Dateinamen. Die Sortierung ist natürlich, d.h. sie kommt korrekt mit Zahlen in Namen zurecht und gibt z.B. foo1.txt vor foo10.txt zurück.

Der Finder ermöglicht auch die Sortierung mithilfe einer eigenen Funktion. Diese erhält als Parameter zwei Nette\Utils\FileInfo-Objekte und muss das Ergebnis des Vergleichs mit dem Operator <=> zurückgeben, also -1, 0 oder 1. Zum Beispiel sortieren wir so Dateien nach Größe:

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

Mehrere verschiedene Suchen

Wenn Sie mehrere verschiedene Dateien an verschiedenen Orten finden müssen oder solche, die andere Kriterien erfüllen, verwenden Sie die Methode append(). Sie gibt ein neues Finder-Objekt zurück, sodass Methodenaufrufe verkettet werden können:

($finder = new Finder) // wir speichern den ersten Finder in der Variablen $finder!
	->files('*.php')   // in src/ suchen wir nach *.php Dateien
	->from('src')
	->append()
	->files('*.md')    // in docs/ suchen wir nach *.md Dateien
	->from('docs')
	->append()
	->files('*.json'); // im aktuellen Ordner suchen wir nach *.json Dateien

Alternativ kann die Methode append() verwendet werden, um eine bestimmte Datei (oder ein Array von Dateien) hinzuzufügen. Dann gibt sie dasselbe Finder-Objekt zurück:

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

FileInfo

Nette\Utils\FileInfo ist eine Klasse, die eine Datei oder ein Verzeichnis in den Suchergebnissen darstellt. Es handelt sich um eine Erweiterung der Klasse SplFileInfo, die Informationen wie Dateigröße, letztes Änderungsdatum, Name, Pfad usw. bereitstellt.

Zusätzlich bietet sie Methoden zum Zurückgeben des relativen Pfads, was beim Tiefendurchlauf nützlich ist:

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

Weiterhin stehen Ihnen Methoden zum Lesen und Schreiben des Dateiinhalts zur Verfügung:

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

Rückgabe der Ergebnisse als Array

Wie in den Beispielen gezeigt, implementiert der Finder das Interface IteratorAggregate, sodass Sie foreach verwenden können, um die Ergebnisse zu durchlaufen. Er ist so programmiert, dass die Ergebnisse nur während des Durchlaufens geladen werden. Wenn Sie also eine große Anzahl von Dateien haben, wird nicht gewartet, bis alle gelesen wurden.

Sie können sich die Ergebnisse auch als Array von Nette\Utils\FileInfo-Objekten zurückgeben lassen, und zwar mit der Methode collect(). Das Array ist nicht assoziativ, sondern numerisch.

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