Lerne Coding
Moderne Methoden, um Verzeichnisse in PHP auszulesen
23.12.2020

PHP Verzeichnisse einlesen bzw. auslesen

Inhaltsverzeichnis
[[TABLE OF CONTENTS]]
access_timeGeschätzte Lesezeit ca. Minuten

PHP wurde, genau wie C oder C++, historisch sehr geprägt. Das bedeutet, es existiert im Internet viel veralteter Code, der gar nicht die Vorteile der neueren Versionen nutzt, so auch beim Einlesen von Verzeichnissen. Wenn man sich das Beispiel auf der offiziellen PHP Seite durchliest, dann wird zunächst ein Stream geöffnet, welcher dann nach und nach verarbeitet wird. Nicht nur, dass diese Methode keiner nutzen möchte, es entstehen auch schnell Fehler und Missverständnisse darüber, was der PHP Code überhaupt treibt. Es wäre schön, wenn es eine Funktion gäbe, welche mir die Ordner einfach nur noch ausgeben würde! Ich möchte dir deshalb nun drei Möglichkeiten zeigen, um ein Verzeichnis auszulesen.

Veraltet: readdir()

Diese Version ist nicht gut, da hier darauf plädiert wird, dass der Wert der Zeile nicht false ist, leere Strings in PHP sind jedoch false. Solltest du also eine leere Zeile haben, bricht diese das Skript ab.

if ($handle = opendir('/home/uvulpos/Desktop')) {
    while ($entry = readdir($handle)) {
        echo "$entry\n";
    }

    closedir($handle);
}

Umständlich: Filestreams API

Funktioniert noch, ist aber sehr umständlich und schlecht lesbar. Zudem ist die Arbeit mit Filestreams in PHP unlesbar und sehr umständlich.

if ($handle = opendir('/home/uvulpos/Desktop')) {
    while (false !== ($entry = readdir($handle))) {
          if ($file === ".." or $file === ".") continue;
        echo "$entry\n";
    }

    closedir($handle);
}

Bevorzugt: scandir()

Die Funktion scandir() gibt alle Verzeichnisse und Dateien innerhalb eines Pfades als Array zurück. Ich entferne hier . und .., da diese keine richtigen Verzeichnisse sind.

foreach (scandir("/home/uvulpos/Desktop") as $file) {
  if ($file === ".." or $file === ".") continue;

    echo "$file\n";
}

Tipp

Wenn du . und .. direkt aus dem Array Entfernen willst, das durch scandir erstellt wird, kann ich dir die Funktion array_diff empfehlen!

$files = array_diff(scandir('/home/uvulpos/Desktop'), ['.','..']);

Fazit

Viele Entwickler suchen im Internet immer nur nach der ersten Lösung und produzieren deswegen sehr schlecht lesbaren Code. Zudem ist alles, was PHP intern berechnet, schneller und performanter als äquivalente Funktionen, die über die Sprache definiert wurden. Versuche deshalb, solange du nur eine einfache Funktion wie die Rückgabe aller Verzeichnisse benötigst, so wenige Streams wie möglich zu verwenden.

Bildquelle - Vielen Dank an die Ersteller:innen für dieses Bild
Kommentare zum Artikel
Andreas schreibt ... Kommentar vom 06.05.2022
Punkte...

Super, vielen Dank, das klappt sehr gut und mit kurzen Code! Benutze es jetzt zum Auslesen von Fotos auf meinem Fotoblog. Aber wozu sollte man "." und ".." aus dem Array entfernen – um keine Ordner aufgelistet zu bekommen? Ich habe dafür if (preg_match("/.jpg$/", $file)) gesetzt, um sicherzustellen, dass nur meine jpgs angezeigt werden. Würde die genannte Funktion array_diff denselben Zweck erfüllen (und nur alles außer Ordnern anzeigen, verstehe ich das richtig)? Entschuldige meine dummen Fragen, ich lerne auf meine alten Tage gerade erst, php einigermaßen zu verstehen. DANKE!

Antworten
Andreas
Antwort von Felix Schürmeyer Kommentar vom 06.05.2022
Re: Punkte...

Guten Tag,

natürlich kannst du auch mit Preg Match direkt spezifische Datei-Typen dir nur holen. Bei dem obenstehenden Fall geht es darum das beim Lesen von Verzeichnissen in PHP auch immer das aktuelle Verzeichnis also '.' und das übergeordnete Verzeichnis '..' ausgegeben werden und in vielen Fällen benötigt man diese beiden angaben nicht.

Kleiner Tipp noch, mit deiner Abfrage holst du nur Bilder mit der Dateiendung .jpg. Allerdings gibts ja auch JPG-Dateien mit der Dateiendung jpeg diese solltest du auch Prüfen. Und wenn du es noch sicherer machen willst, solltest du den Mime Type prüfen, den die Dateiendung muss nicht immer mit dem Dateityp überein stimmen.

Gruß, Felix

Felix Schürmeyer
Antwort von Tim Riedl Kommentar vom 23.05.2022
RE: Punkte...

Hallo Andreas,

zusätzlich vom Kommentar von Felix möchte ich noch anmerken, dass sogar PHP selbst diese Praxis in ihren Tests ausübt, z.B. hier https://github.com/php/php-src/blob/5b01c4863fe9e4bc2702b2bbf66d292d23001a18/ext/standard/tests/file/windows_mb_path/bug75063_utf8.phpt#L63. Falls du es ganz genau wissen möchtest, sie dir auch gerne die Implementierung der Funktion an 😉 https://github.com/php/php-src/blob/01b3fc03c30c6cb85038250bb5640be3a09c6a32/main/php_scandir.c#L52

Anders als Felix würde ich mich jedoch nicht auf den Mime-Type stützen sondern es als eine Art unverbindliche Dateiendungsempflung handhaben. Nur weil JPEG drauf steht, muss kein JPEG drin sein, gerade wenn die Datei vom User kommt 😉

Tim Riedl
Rolf Borchmann schreibt ... Kommentar vom 13.05.2022
Ich bin nicht so ganz einverstanden

Hallo Tim,

meine Muttersprache ist zwar nicht PHP, sondern eher C#, aber ich glaube, Du hast hier Überarbeitungsbedarf.

(1) Die readdir Funktion ist alt (PHP4), aber deswegen nicht veraltet. Das falsy-Thema ist kein Problem von readdir. Sicher, eine Datei namens "0" würde im gezeigten Code die Schleife abbrechen. Aber das liegt am falschen Gebrauch von readdir. Wenn ich weiß, dass ein API einen Wert ODER false liefert, dann ist das korrekte PHP-Idiom, den Rückgabewert mit einem Identitätsvergleich gegen FALSE zu testen (also while (false !== ($entry = readdir($handle)))). Genau wie bei den SQL Funktionen.

Gegen readdir spricht der gleiche Grund wie gegen scandir: Man kann im output Dateien und Verzeichnisse nicht unterscheiden und muss is_dir / is_file aufrufen.

(2) Das FileStream Beispiel ähnelt verdächtig der readdir Variante ;-)

(3) Was ist mit glob()? Da habe ich das Problem mit . und .. nicht. Dazu kann ich mit GLOB_MARK dafür sorgen, dass ich im Ergebnis Dateien und Ordner unterscheiden kann, und muss nicht für jede Datei is_dir aufrufen, um das zu prüfen.

(4) WAS IST MIT DEM DirectoryIterator??? Der ist auch schon seit PHP 5 dabei, und der bietet eine OO Schnittstelle.

Vom Tempo her sind sie alle gleich - WENN man prüft, ob man eine Datei oder ein Verzeichnis gefunden hat (bei glob durch GLOB_MARK). Ohne diese Prüfung ist glob unendlich viel langsamer,

Antworten
Rolf Borchmann
Antwort von Tim Riedl Kommentar vom 23.05.2022
RE: Ich bin nicht so ganz einverstanden

Hallo Rolf,

danke für deinen Kommentar, den DirectoryIterator hatte ich da tatsächlich vergessen, den werde ich die Tage noch ergänzen, guter Punkt 👍 . Zu den anderen Punkten:

1) "Veraltet" ist evtl. ein etwas provozierender Begriff da geb ich dir recht, aber er sollte auch etwas provokant wirken damit man sich als Unwissender mit dem Thema mal auseinandersetzt. Dennoch ist es ja keine Falschinformation, da scandir ja eine PHP Major Version später kam. Über das Thema kann man sich aber jetzt sicherlich streiten, aber das ist ein etabliertes Mittel was auch seriöse Zeitungen, Fernsehen und Radio und nicht nur Boulevard-Medien verwenden.

2) Ich sehe häufig privat / geschäftlich / im Netz Code, der wie du richtig sagst, die readdir Funktion falsch verwendet und somit Fehler oder unsauberer Code durch workarounds entsteht. scandir kann zwar auch false zurückgeben, aber nur wenn das Verzeichnis nicht existiert. Gibst du einer foreach Schleife eine Variable mit dem Wert false an, wird sie einfach übersprungen. Auch werden hier Dateien wie zB "0" korrekt angezeigt.

3) while (false !== ($entry = readdir($handle))) entspricht nicht mehr wirklich dem aktuelle, PHP-Idiom, auch nicht mehr für SQL da man hier bei z.B. Laravel Eloquent auch eher Richtung Arrays geht siehe z.B. https://www.itsolutionstuff.com/post/laravel-eloquent-selectraw-query-exampleexample.html (erster Treffer meiner Suche), es widerspricht meines Erachtens sogar gegen die Clean Code Regeln das hier eine verschachtelte Bedingung geschrieben werden muss, sowie sich um einen Stream gekümmert werden muss, der bei scandir automatisch gehandhabt wird. Der führt zu ausführlichem Code aber nicht zu kontrollierteren. Code soll ja laut Philosophie einfach, verständlich, kontrollierbar, sicher und und pregnant sein 🙂

4) Die glob() kann sicherlich optional noch zum Artikel hinzugefügt werden, jedoch wäre das für mich dann nochmal ein etwas anderes Thema. Da geht es dann schon um die Art und Weise der Verwendung der Dateien.

5) Bzgl der Filestream API: Ja da ist was beim kopieren falsch gelaufen, das pass ich dann im nächsten Update mit an, wenn ich den DirectoryIterator hinzufüge. Das ist mir noch nicht aufgefallen 😅

Ich hoffe mit diesem Kommentar etwas mehr Zufriedenheit geschaffen zu haben 🙂

Tim Riedl
Kommentar schreiben

Vom Autor Empfohlen
close