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 der folgende Alias definiert 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. Der Schlüssel $name enthält den 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 und können daher ohne Erstellung einer Instanz aufgerufen werden. 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 leeres 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 behandelt.

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 angegebenen Verzeichnissen gesucht. Wenn eines der Verzeichnisse nicht existiert, wird eine Nette\UnexpectedValueException ausgelöst.

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.

Bei der Tiefensuche von Dateien und Verzeichnissen wird standardmäßig zuerst das übergeordnete Verzeichnis und dann dessen Inhalt zurückgegeben. Dieses Verhalten kann mit childFirst() umgekehrt werden.

Platzhalter

In der Maske können Sie folgende 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 in den Verzeichnissen 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 bestimmter Unterverzeichnisse zu überspringen:

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

Filterung

Der Finder bietet mehrere Methoden zum Filtern (d.h. Reduzieren) der Ergebnisse. 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 Methoden unterstützen die Operatoren >, >=, <, <=, =, !=, <>.

Der Finder ermöglicht auch das Filtern der Ergebnisse mithilfe benutzerdefinierter Funktionen. Die Funktion erhält als Parameter ein Nette\Utils\FileInfo-Objekt 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 Suchtiefe 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 (Standard) hebt das Limit auf.

Der Finder ermöglicht es, mithilfe benutzerdefinierter Funktionen zu entscheiden, welche Verzeichnisse während der Suche betreten werden sollen. Die Funktion erhält als Parameter ein Nette\Utils\FileInfo-Objekt, das das Verzeichnis repräsentiert, und muss true zurückgeben, damit in das Verzeichnis eingetreten wird:

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

Sortierung

Der Finder bietet auch mehrere Funktionen zum Sortieren der Ergebnisse.

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

Der Finder ermöglicht auch die Sortierung mithilfe einer benutzerdefinierten 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 unterschiedliche Dateien an verschiedenen Orten oder mit unterschiedlichen Kriterien finden müssen, 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 Variable $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. In diesem Fall 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 repräsentiert. Sie erweitert die Klasse SplFileInfo und stellt Informationen wie Dateigröße, letztes Änderungsdatum, Name, Pfad usw. bereit.

Zusätzlich bietet sie Methoden zur Rückgabe des relativen Pfads, was bei der Tiefensuche nützlich ist:

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

Außerdem stehen 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 IteratorAggregate-Interface, sodass Sie foreach verwenden können, um die Ergebnisse zu durchlaufen. Er ist so implementiert, dass die Ergebnisse erst während des Durchlaufens geladen werden (Lazy Loading). Wenn Sie also eine große Anzahl von Dateien haben, müssen Sie nicht warten, bis alle eingelesen sind.

Mit der Methode collect() können Sie die Ergebnisse auch als Array von Nette\Utils\FileInfo-Objekten erhalten. Das Array ist numerisch indiziert (nicht assoziativ).

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