PHP/Tutorials/Rekursives Löschen von Verzeichnissen

Aus SELFHTML-Wiki
< PHP‎ | Tutorials
Wechseln zu: Navigation, Suche

Irgendwann stößt man auf das Problem in PHP, dass man ein Verzeichnis löschen will, das jedoch schon einen Inhalt besitzt. rmdir versagt dabei jedoch, da es nur leere Verzeichnisse löschen kann.

Hier wird nun eine Funktion vorgestellt, die Verzeichnisse rekursiv abarbeitet und zuerst alle Dateien löscht. Danach wird das Verzeichnis selbst mit rmdir gelöscht.

Anwendungsbeispiel

recrmdir.inc.php
<?php

// rec_rmdir - loesche ein Verzeichnis rekursiv
// Rueckgabewerte:
//   0  - alles ok
//   -1 - kein Verzeichnis
//   -2 - Fehler beim Loeschen
//   -3 - Ein Eintrag eines Verzeichnisses war keine Datei und kein Verzeichnis und
//        kein Link
function rec_rmdir ($path) {
    // schau' nach, ob das ueberhaupt ein Verzeichnis ist
    if (!is_dir ($path)) {
        return -1;
    }
    // oeffne das Verzeichnis
    $dir = @opendir ($path);
    
    // Fehler?
    if (!$dir) {
        return -2;
    }
    
    // gehe durch das Verzeichnis
    while ($entry = @readdir($dir)) {
        // wenn der Eintrag das aktuelle Verzeichnis oder das Elternverzeichnis
        // ist, ignoriere es
        if ($entry == '.' || $entry == '..') continue;
        // wenn der Eintrag ein Verzeichnis ist, dann 
        if (is_dir ($path.'/'.$entry)) {
            // rufe mich selbst auf
            $res = rec_rmdir ($path.'/'.$entry);
            // wenn ein Fehler aufgetreten ist
            if ($res == -1) { // dies duerfte gar nicht passieren
                @closedir ($dir); // Verzeichnis schliessen
                return -2; // normalen Fehler melden
            } else if ($res == -2) { // Fehler?
                @closedir ($dir); // Verzeichnis schliessen
                return -2; // Fehler weitergeben
            } else if ($res == -3) { // nicht unterstuetzer Dateityp?
                @closedir ($dir); // Verzeichnis schliessen
                return -3; // Fehler weitergeben
            } else if ($res != 0) { // das duerfe auch nicht passieren...
                @closedir ($dir); // Verzeichnis schliessen
                return -2; // Fehler zurueck
            }
        } else if (is_file ($path.'/'.$entry) || is_link ($path.'/'.$entry)) {
            // ansonsten loesche diese Datei / diesen Link
            $res = @unlink ($path.'/'.$entry);
            // Fehler?
            if (!$res) {
                @closedir ($dir); // Verzeichnis schliessen
                return -2; // melde ihn
            }
        } else {
            // ein nicht unterstuetzer Dateityp
            @closedir ($dir); // Verzeichnis schliessen
            return -3; // tut mir schrecklich leid...
        }
    }
    
    // schliesse nun das Verzeichnis
    @closedir ($dir);
    
    // versuche nun, das Verzeichnis zu loeschen
    $res = @rmdir ($path);
    
    // gab's einen Fehler?
    if (!$res) {
        return -2; // melde ihn
    }
    
    // alles ok
    return 0;
}

?>


Das Beispiel
<?php
    // importiere die Datei mit der Funktion
    require 'recrmdir.inc.php';
    
    // loesche das Verzeichnis /tmp/test_verzeichnis
    $res = rec_rmdir ('/tmp/test_verzeichnis');
    // wurde das Verzeichnis korrekt gelöscht
    switch ($res) {
      case 0:
        // das Verzeichnis wurde korrekt gelöscht
        break;
      case -1:
        // das war kein Verzeichnis
        break;
      case -2:
        // ein Fehler ist aufgetreten
        break;
      case -3:
        // die Funktion ist über einen Dateityp gestolpert, den sie nicht kennt
        break;
      default:
        // die Funktion hat irgend etwas zurückgegeben, was sie eigentlich nicht sollte
        break;
    }
?>

Die Funktion rec_rmdir erwartet einen Parameter $path. Dieser Parameter enthält das Verzeichnis, das zu löschen ist. Als erstes wird in der Funktion geprüft, ob der angegebene Pfad auch wirklich existiert und ein Verzeichnis ist. Wenn nicht, wird der Rückgabewert -1 zurückgegeben.

Daraufhin wird das Verzeichnis mit Hilfe der opendir-Funktion geöffnet. Wenn dieser Aufruf fehlschlägt, dann wird -2 zurückgegeben. Dieser Aufruf kann nur dann fehlschlagen, wenn der Benutzer nicht genügend Dateirechte hat, um auf dieses Verzeichnis zuzugreifen.

Danach wird mit einer while-Schleife das Verzeichnis Eintrag für Eintrag abgearbeitet. Da readdir auch das aktuelle Verzeichnis (.) und das Elternverzeichnis (..) zurückgibt, muss sofort geprüft werden, ob der zurückgelieferte Eintrag nicht eines der beiden ist. Wenn dies zutrifft, werden diese ignoriert, denn sonst würde man eine Endllosschleife erzeugen.

Jetzt wird geprüft, ob der Eintrag ein Verzeichnis ist. (if (is_dir ($path.'/'.$entry))) Wenn das der Fall ist, ruft die Funktion sich selbst auf mit diesem Eintrag als Parameter. Der Rückgabewert wird auf Fehler überprüft. Wenn der Rückgabewert -1 ist, dann dürfte das eigentlich nicht passieren, da eine Zeile zuvor überprüft wurde, ob das ein Verzeichnis ist und dieser Rückgabewert von rec_rmdir zurückgegeben wird, wenn das kein Verzeichnis ist. Dieser Fehler wird hier stillschweigend ignoriert und es wird abgebrochen, indem -2 zurückgegeben wird. Dieser Rückgabewert bedeutet, dass irgendein Fehler beim Löschen selbst aufgetreten ist. Wenn der Aufruf der Funktion -2 oder -3 (es gab ein Objekt im Dateisystem, das diese Funktion nicht kennt) zurückliefert wird das "weitergegeben". Wenn der Rückgabewert der Funktion jetzt noch etwas anderes außer 0 (alles in Ordnung) ist, dann wird wieder der generische Fehlercode -2 zurückgegeben. Vor dem Zurückgeben des Fehlercodes wird das Verzeichnis noch geschlossen.

Falls dieser Eintrag eine Datei (else if (is_file ($path.'/'.$entry)) oder ein symbolischer Link (|| is_link ($path.'/'.$entry))) ist, dann wird versucht, diese Datei normal per unlink zu löschen. Falls dies fehlschlägt, wird das Verzeichnis geschlossen und der generische Fehlercode -2 zurückgegeben.

Wenn dieser Eintrag werder ein Verzeichnis noch eine Datei noch ein symbolischer Link ist (else), dann wird der Fehlercode -3 zurückgegeben. Dies dürfte bei normalen Anwendungen nicht auftreten.

Schließlich wird das Verzeichnis geschlossen. Der Rückgabewert wird nicht überprüft, da das Ergebnis dieses Vorgangs nicht für die weitere Ausführung kritisch ist. (Wenn das Verzeichnis nicht korrekt geschlossen werden konnte, dann ist es noch offen und das Löschen schlägt fehl, daher ist es redundant, hier eine Fehlerbehandlung einzuführen)

Am Ende wird mit der rmdir-Funktion das Verzeichnis gelöscht. Wenn diese Funktion einen Fehler liefert, dann wird der generische Fehlercode -2 zurückgeliefert. Falls dies nicht der Fall war, wird der Code 0 zurückgeliefert, was signalisiert, dass die Funktion erfolgreich war.

Beachten Sie: Das PHP-Script, in der diese Funktion aufgerufen wird, muss genügend Dateirechte besitzen, um das Verzeichnis inklusive dessen kompletten Inhalts zu löschen. Eine auf jeden Fall funktionierende Variante ist die Reche des Verzeichnisses und aller enthaltenen Unterverzeichnisse und Dateien auf 777 zu setzen. Dies ist jedoch eine schlechte Idee, da damit jeder, der Zugriff auf den Serverrechner hat, mit diesen Dateien und Verzeichnissen anstellen kann, was er will. Daher ist es nur zu empfehlen, wenn keine andere Möglichkeit funktioniert.

Bei den Aufrufen der Dateisystemfunktionen wurde immer der Operator @ verwendet. Dieser unterdrückt alle Fehlermeldungen, die diese Funktion eventuell ausgibt. Es ist im Allgemeinen nicht sinnvoll, Fehlermeldungen über Systemfunktionen an den Benutzer weiterzugeben, dies könnte ein Sicherheitsrisiko darstellen. Bei einer korrekten Serverkonfiguration ist log_errors aktiviert und alle Fehler werden mitprotokolliert und können vom Betreuer nachgelesen werden. Bei der Entwicklung von Webanwendungen kann es jedoch von Vorteil sein, dass die Fehlermeldungen angezeigt werden. Daher kann dann dieser Operator entfernt werden.

Es werden nur Dateien, symbolische Links und Verzeichnisse gelöscht. Wenn zusätzlich auch noch Gerätedateien, FIFOs oder Sockets gelöscht werden sollen, dann muss die entsprechende Abfrage entfernt werden.

Weblinks

Die folgenden Stellen werden empfohlen, um das obige Beispiel besser zu verstehen, oder um weitere Möglichkeiten und Details zu erfahren.