# Dateisystem

Wenn die Auslagerungsdatei auf Laufwerk E: eine Anfangsgröße von weniger als 0 MB hat, wird das System möglicherweise keine Debuginformationen speichern können, wenn ein »STOP«-Fehler auftritt.
– Meldung von Windows XP Home

Der Zugriff auf Verzeichnisse und Dateien unter PHP funktioniert ähnlich einfach wie in Perl. Allerdings steht Ihnen hier nicht die Möglichkeit der kurzen Steuerzeichen zur Verfügung. In PHP müssen Sie sich Ihren Weg über verschiedene Funktionen suchen. Dass dies jedoch sehr einfach ist, wird Ihnen dieses Kapitel schildern.

# Verzeichnisliste

Da PHP sehr stark auf die Objektorientierung ausgerichtet ist, wurde für den Zugriff auf Verzeichnisse eine eigene (Pseudo-)Klasse definiert, die über alle Eigenschaften und Methoden verfügt, die erforderlich sind. Diese Klasse heißt dir.

Eigenschaften der Klasse dir:

  • handle
    Enthält die Zugriffsnummer für das aktuelle Verzeichnis.
  • path
    Enthält den Pfad des aktuellen Verzeichnisses.

Methoden der Klasse dir:

  • dir
    Der Konstruktor der Klasse; ihm wird das zu öffnende Verzeichnis übergeben.
  • read
    Liest nacheinander alle Verzeichniseinträge aus.
  • rewind
    Setzt den Verzeichniszeiger auf den ersten Verzeichniseintrag zurück.
  • close
    Gibt das Verzeichnis-Handle frei und schließt das Verzeichnis.

Das folgende Listing soll lediglich das aktuelle Verzeichnis öffnen und alle Verzeichniseinträge im Browser ausgeben.

<?php

  $d = dir(".");
  echo "<h1>$d->path</h1>";
  while($eintrag = $d->read())
  {
    echo "$eintrag<br>\n";
  }
  $d->close();

?>

Listing 7.1: Einlesen des aktuellen Verzeichnisses und Ausgabe der Einträge im Browser

Das aktuelle Verzeichnis erhalten Sie über den symbolischen Eintrag ».«. Bei Instanziierung des Objekts $d wird dem Konstruktor der Klasse dieser symbolische Eintrag übergeben.

$d = dir(".");

Da die Methode read mit einem Zeiger vergleichbar ist, können Sie sie als Bedingung in einer while-Schleife aufrufen. Der Zeiger steht anfangs auf dem ersten Verzeichniseintrag und springt einen Eintrag weiter, sobald die Methode read aufgerufen wird. Als Rückgabewert liefert sie den aktuellen Eintrag. Ist das Ende erreicht, gibt die Methode FALSE zurück, und die Schleife wird beendet. Der aktuelle Eintrag wird der Variablen $eintrag zugewiesen und im Anweisungsblock der while-Schleife ausgegeben. Nachdem die Schleife beendet wurde, wird auch das Verzeichnishandle wieder freigegeben und das Verzeichnis geschlossen.

$d->close();

# Verzeichnis oder Datei

Mit der Funktion is_dir können Sie überprüfen, ob der als Parameter übergebene Name einem Verzeichnis oder einer Datei entspricht. Ist es ein Verzeichnis, wird TRUE zurückgegeben, andernfalls FALSE.

bool is_dir(string pathname)

Das Script aus Listing 7.1 wurde nun um diese Funktion erweitert und gibt den Eintrag fett gedruckt aus, wenn es sich um ein Verzeichnis handelt.

<?php

  $d = dir(".");
  echo "<h1>$d->path</h1>";
  while($eintrag = $d->read())
  {
    if(is_dir($eintrag))
    {
      echo "<b>$eintrag</b><br>\n";
    }
    else
    {
      echo "$eintrag<br>\n";
    }
  }
  $d->close();

?>

Listing 7.2: Unterschiedliche Formatierung der Ausgabe

Im Anweisungsblock der while-Schleife wird nun mit Hilfe der Funktion is_dir überprüft, ob der Eintrag ein Verzeichnis ist, und falls ja, wird er dann fett gedruckt ausgegeben.

Das Einlesen eines Verzeichnisses ist jedoch nicht nur auf das Basisverzeichnis des Webservers beschränkt. Im Prinzip können Sie jedes beliebige Verzeichnis auf dem Server öffnen und einlesen. Sie müssen lediglich das entsprechende Verzeichnis entweder relativ zum aktuellen Verzeichnis oder absolut angeben. Denken Sie aber daran, dass sich hier eine Sicherheitslücke auftut, wenn der Benutzer auf alle Dateien und Verzeichnisse des Servers zugreifen darf.

# Alternative

Alternativ zur Verwendung der Klasse dir können Sie auch nur mit Funktionen arbeiten, die ein Verzeichnis öffnen, einlesen und wieder schließen.

Die Funktion opendir öffnet das als Parameter übergebene Verzeichnis und gibt ein Handle für dieses Verzeichnis zurück.

int opendir(string path)

Mit der Funktion readdir können Sie ein Verzeichnis-Eintrag für Eintrag auslesen. Als Parameter muss das mit opendir erzeugte Verzeichnishandle übergeben werden. Der aktuelle Eintrag wird dann von der Funktion zurückgegeben.

string readdir(int handle)

Das Äquivalent zur Methode close der Klasse dir ist closedir. Sie erwartet als Parameter das Verzeichnishandle, gibt das Handle frei und schließt das Verzeichnis wieder.

void closedir(int handle)

Das umgeschriebene Script aus Listing 7.1 sieht dann folgendermaßen aus:

<?php

  $dirhandle = opendir(".");
  while($eintrag = readdir($dirhandle))
  {
    echo "$eintrag<br>\n";
  }
  closedir($dirhandle);

?>

Listing 7.3: Einlesen und Ausgeben des aktuellen Verzeichnisses mit Funktionen

# Weitere Verzeichnisfunktionen

Darüber hinaus gibt es noch einige weitere Funktionen, die das Arbeiten mit Verzeichnissen ermöglichen.

Die Funktion rewinddir z. B. setzt den Verzeichniszeiger wieder auf den Anfang zurück und entspricht der Methode rewind der Klasse dir. Als Parameter erwartet die Funktion das Verzeichnishandle.

void rewinddir(int handle)

Das aktuelle Verzeichnis können Sie übrigens auch mit der Funktion getcwd auslesen, anstatt den symbolischen Link zu verwenden. Sie erwartet keinen Parameter und gibt das aktuelle Arbeitsverzeichnis als String zurück.

string getcwd(void)

Die Funktion chdir wechselt vom aktuellen in dasjenige Verzeichnis, das als Parameter übergeben wurde. Wurde die Operation erfolgreich durchgeführt, gibt die Funktion TRUE zurück, andernfalls FALSE.

bool chdir(string directory)

# Rekursion

Wie auch in Abschnitt 36.2, Rekursion beschrieben, ist eine Rekursion eine Unterteilung einer Hauptaufgabe in mehrere kleinere Teilaufgaben. Die Funktion, die die Hauptaufgabe lösen soll, nimmt diese Unterteilung vor und ruft sich selbst auf, um die Teilaufgabe lösen zu können. Nachfolgend finden Sie die Umsetzung des Perl-Scripts aus Abschnitt 36.2 in PHP. Damit der Wiedererkennungswert jedoch höher ist, wurde beim Auslesen der Verzeichnisse die alternative Variante mit den Funktionen verwendet.

<?php

  // Definition der Funktion Unterverzeichnis
  function Unterverzeichnis($subverz)
  {
    echo "<tr><td>$subverz</td><td>";
    if($dir = opendir($subverz))
    {
      $verz = Array();
      while($eintrag = readdir($dir))
      {
        if(($eintrag <> '.') AND ($eintrag <> '..'))
        {
          if(is_dir("$subverz/$eintrag"))
          {
            $verz[] = $eintrag;
          }
          else
          {
            $groesse = filesize("$subverz/$eintrag");
            echo "<a href=\"$subverz/$eintrag\">$eintrag</a> ($groesse Bytes)<br>";
          }
        }
      }
      echo "</td></tr>";
      foreach($verz as $akt_verz)
      {
        Unterverzeichnis("$subverz/$akt_verz");
      }
      closedir($dir);
    }
  }

  // Ermitteln des Wurzelverzeichnisses
  $wverz = $_SERVER['DOCUMENT_ROOT'];

  // Ausgabe des Anfangs des HTML-Dokumentes
  echo "<html><head><title>Wurzelverzeichnis:$wverz</title></head><body>";
  echo "<h1>Wurzelverzeichnis: $wverz</h1><table border=\"1\">";

  // Erster Aufruf der Funktion "Unterverzeichnis"
  Unterverzeichnis($wverz);

  // Abschluss des HTML-Dokumentes
  echo "</table></body></html>";

?>

Listing 7.4: Rekursives Einlesen von Verzeichnissen mit PHP

Eine Erklärung des Listings finden Sie in Abschnitt 36.2.

# Datei schreiben

Der Zugriff auf Dateien, egal ob lesend oder schreibend, wird immer durch das Öffnen der Datei eingeleitet. Die Funktion fopen öffnet Dateien und erwartet als ersten Parameter den Dateinamen und als zweiten den Modus, wie auf die Datei zugegriffen werden soll. Konnte der Vorgang erfolgreich durchgeführt werden, liefert die Funktion ein Handle für den Zugriff auf die Datei zurück.

int fopen(string filename, string mode)

Ein Beispielaufruf der Funktion:

$file = fopen("datei.dat","w");

Die Funktion fopen versucht nun, die Datei datei.dat zum Schreiben zu öffnen. Dass in die Datei geschrieben werden soll, wird der Funktion durch das w (engl. write = dt. schreiben) als zweiten Parameter mitgeteilt. In der Variablen $file wird bei Erfolg das Handle zum Zugriff auf die Datei gespeichert.

Der Schreibvorgang selbst erfolgt durch Verwendung der Funktion fputs. Diese Funktion erwartet als ersten Parameter das Datei-Handle und als zweiten die Zeichenkette, die in die Datei geschrieben werden soll.

int fputs(int handle, string str)

War der Vorgang erfolgreich, liefert die Funktion die Anzahl der geschriebenen Zeichen zurück, andernfalls –1. Dieses Verhalten kann zur Überprüfung verwendet werden, ob alle Zeichen in die Datei geschrieben wurden, diese Überprüfung wird jedoch in der Regel nicht vorgenommen. Ein Beispiel:

fputs($file,"Hallo Welt!");

Nachdem alle Schreiboperationen durchgeführt worden sind, muss die Datei wieder geschlossen werden. Dies erledigt die Funktion fclose. Auch sie erwartet als Parameter das Dateihandle. Konnte die Datei geschlossen werden, lautet das Ergebnis der Funktion TRUE, ansonsten FALSE.

bool fclose(int handle)

Ein Beispiel:

fclose($file);

Nun zu einem Beispiel, das die Funktionen im Zusammenhang darstellt. In eine Datei sollen lediglich zwei Zeilen geschrieben werden. Diese Datei wird anschließend mit der include-Funktion im Browser ausgegeben.

<?php

  // Schreiben der Datei datei.dat
  $file = fopen("datei.dat","w");
  if($file)
  {
    fputs($file,"Hallo Welt!\n");
    fputs($file,"Hallo WorldWideWeb!\n");
    fclose($file);
  }
  
  // Ausgabe der Datei datei.dat im Browser
  echo "<h3>Inhalt der Datei datei.dat</h3>";
  echo "<pre>";
  include("datei.dat");
  echo "</pre>";

?>

Listing 7.5: Schreiben der Datei datei.dat und Ausgabe im Browser

Zu Beginn des Scripts wird versucht, datei.dat zum Schreiben zu öffnen. Wurde die Datei geöffnet, werden zwei Zeilen in die Datei geschrieben und das Dateihandle wieder freigegeben. Auch beim Schreiben in Dateien können Sie die Escape-Sequenzen verwenden. In diesem Fall erzeugt \n einen Zeilenumbruch. Zum Schluss wird die Datei innerhalb eines pre-Elements ausgegeben.

# Datei lesen

Neben dem Modus w zum Schreiben von Dateien gibt es auch noch folgende weitere Modi.

Modus Erklärung
r Die Datei wird nur zum Lesen geöffnet. Der Dateizeiger wird auf den Anfang der Datei gesetzt.
r+ Die Datei wird zum Lesen und Schreiben geöffnet. Der Dateizeiger wird auf den Anfang der Datei gesetzt.
w Die Datei wird nur zum Schreiben geöffnet. Existiert die Datei bereits, wird sie überschrieben, andernfalls neu erstellt.
w+ Öffnet die Datei zum Lesen und Schreiben. Existiert die Datei bereits, wird sie überschrieben, andernfalls neu erstellt.
a Öffnet die Datei nur zum Schreiben. Der Dateizeiger wird auf das Ende der Datei gesetzt. Sollte die Datei noch nicht existieren, wird sie neu erstellt.
a+ Öffnet die Datei zum Schreiben und Lesen und setzt den Dateizeiger auf das Ende der Datei. Existiert die Datei nicht, wird sie neu angelegt.

Tabelle 7.1: Zugriffsmodi für Dateien

Im Zusammenhang mit dem Lesen von Dateien gibt es einige hilfreiche Funktionen. Die wichtigste von allen ist wohl die Funktion fgets, da sie das Lesen aus einer Datei ermöglicht. Als ersten Parameter erwartet sie das Dateihandle und als zweiten die Angabe, wie viele Zeichen aus der Datei ausgelesen werden sollen. Als Rückgabewert liefert die Funktion die ausgelesenen Zeichen.

string fgets(int handle[, int length])

Wenn Sie die Länge nicht angeben, wird die Datei bis zum Ende ausgelesen. Ein Beispiel:

$dateistr = fgets($file,255);

Dateien sind jedoch nicht immer genau 255 Byte bzw. Zeichen groß. Sie können auch kleiner oder größer sein. Bei jedem Lesevorgang wird der Dateizeiger um die Anzahl der gelesenen Zeichen verschoben. Dabei liest die Funktion jedoch immer nur so viele Zeichen ein, wie noch übrig sind. Um auf das Ende einer Datei reagieren zu können (der Dateizeiger sitzt am Ende der Datei), können Sie die Funktion feof verwenden. Sie erwartet als Parameter das Dateihandle.

int feof(int handle)

Wurde das Ende der Datei erreicht, liefert die Funktion einen Wert, der ungleich –1 ist.

Äußerst hilfreich ist auch die Funktion file_exists. Mit ihr können Sie die Existenz einer Datei überprüfen. Existiert die als Parameter übergebene Datei, entspricht der Rückgabewert der Funktion TRUE, andernfalls FALSE.

bool file_exists(string filename)

Das folgende Beispiel soll das Lesen und Schreiben einer Datei anhand einer Umfrage darstellen.

<?php

  if($_GET)
  {
    $filename = "abstimmung.txt";
    $wahl = $_GET['os'] - 1;
    if(file_exists($filename))
    {
      $file = fopen($filename,"r");
      if($file)
      {
        $stand = explode("#",fgets($file));
        fclose($file);
        $stand[$wahl]++;
        $file = fopen($filename,"w");
        if($file)
        {
          fputs($file,"$stand[0]#$stand[1]#$stand[2]#$stand[3]");
          fclose($file);
        }
        echo "<h4>Stand</h4>";
        echo "Windows = $stand[0]<br>\n";
        echo "Linux = $stand[1]<br>\n";
        echo "MacOS = $stand[2]<br>\n";
        echo "Anderes = $stand[3]<br>\n";
      }
    }
    else
    {
      echo "$filename existiert nicht!";
    }
  }
  else
  {
    echo "<form action=\"$PHP_SELF\" method=\"GET\">";
    echo "<h4>Welches Betriebssystem verwenden Sie?</h4>";
    echo "<input type=\"radio\" name=\"os\" value=\"1\">Windows<br>\n";
    echo "<input type=\"radio\" name=\"os\" value=\"2\">Linux<br>\n";
    echo "<input type=\"radio\" name=\"os\" value=\"3\">MacOS<br>\n";
    echo "<input type=\"radio\" name=\"os\" value=\"4\">Anderes<br>\n";
    echo "<input type=\"submit\" value=\"Abstimmen\">\n";
    echo "</form>";
  }  

?>

Listing 7.6: PHP-Umfrage-Script, das das Ergebnis in einer Datei speichert

Zu Beginn des Scripts wird überprüft, ob mit einem HTML-Formular und der GET-Methode Daten an das Script übergeben wurden.

Wurden keine Daten übergeben, wird ein HTML-Formular ausgegeben, das als Datenempfänger das eigene PHP-Script erhält. Außerdem wird eine Radiobuttongruppe ausgegeben, die vier Auswahlmöglichkeiten bietet (Windows, Linux, Mac OS, Anderes).

Wenn Daten übermittelt wurden, wird zunächst der Variablen $wahl die übermittelte Auswahl abzüglich 1 zugewiesen. Sie dient später zu Identifikation des richtigen Elements eines Arrays. Anschließend wird überprüft, ob die Datei abstimmung.txt existiert. Wenn nicht, wird eine Fehlermeldung ausgegeben. Ansonsten wird die Datei zum Lesen geöffnet. Es folgt dann eine Kombination von zwei Funktionen. Der Inhalt der Datei, der mit fgets($file) eingelesen wurde, wird als zweiter Parameter an die Funktion explode übergeben. Letztere soll den String, dem sie übergeben wird, anhand des Rautezeichens # in Teile zerlegen, die im Array $stand gespeichert werden. Die Datei wird geschlossen, und das Element mit dem Index, der dem Wert von $wahl entspricht, wird um 1 inkrementiert. Daraufhin wird wieder die Datei abstimmung.txt geöffnet, und alle Elemente des Arrays $stand werden durch Rautezeichen # getrennt in die Datei geschrieben. Nachdem die Datei geschlossen worden ist, wird das aktuelle Abstimmungsergebnis im Browser ausgegeben.

Der Aufbau der Datei abstimmung.txt sieht folgendermaßen aus:

0#0#0#0

Die erste 0 steht für Windows, die zweite für Linux, die dritte für Mac OS und die vierte für Anderes. Die Zahlen werden nach jeder Auswahl eines Radiobuttons um 1 erhöht.

Auch wenn die Möglichkeit besteht, gleichzeitig sowohl lesend als auch schreibend auf eine Datei zuzugreifen, sollten Sie dies nur in den seltensten Fällen tun. Es kann ansonsten passieren, dass Sie den Inhalt der Datei unwiederbringlich löschen.

# Dateieigenschaften

Verschiedene Funktionen geben die unterschiedlichsten Informationen zu einer Datei zurück. Deshalb werden Sie in diesem Kapitel eine Klasse namens fileinfo erstellen, die die Informationen ermittelt und bereitstellt.

Der Quellcode der Klasse:

<?php
  class fileinfo
  {
    var $filename;
    var $size;
    var $type;
    var $lastChange;
    var $lastChangeUnix;
    var $owner;
    var $readable;
    var $writeable;
    var $executable;

    function fileinfo($filename)
    {
      $this->filename = $filename;
      $this->size = filesize($filename);
      $this->type = filetype($filename);
      $this->lastChange = strftime("%d.%m.%Y %H:%M",filemtime($filename));
      $this->lastChangeUnix = filemtime($filename);
      $this->owner = fileowner($filename);
      $this->readable = is_readable($filename);
      $this->writeable = is_writeable($filename);
      $this->executable = is_executable($filename);
    }
  }
?>
<?php

  require('class_fileinfo.php');
  
  $fi = new fileinfo('abstimmung.txt');
  
  echo "Dateiname: $fi->filename<br>\n";
  echo "Gr��e: $fi->size Byte<br>\n";
  echo "Type: $fi->type<br>\n";
  echo "Letzte �nderung: $fi->lastChange<br>\n";
  echo "Besitzer: $fi->owner<br>\n";
  echo "Lesbar: $fi->readable<br>\n";
  echo "Schreibbar: $fi->writeable<br>\n";
  echo "Ausf�hrbar: $fi->executable";

?>

Listing 7.7: Einsatz der Klasse fileinfo

Die Klasse fileinfo besteht lediglich aus einer Funktion und einer Reihe von Variablen. Die Funktion ist der Konstruktor der Klasse, initialisiert die Eigenschaften und wendet dabei die einzelnen Funktionen von PHP an. Die einzige Eigenschaft, die ohne Funktion ermittelt wird, ist natürlich der Dateiname, der in der Eigenschaft $filename gespeichert wird.

Die Dateigröße wird durch die Funktion filesize ermittelt. Sie erhält als Parameter den Dateinamen und liefert die Größe in Bytes zurück.

Die Funktion filetype ermittelt den Typ der Datei und gibt ihn als String zurück. Als Parameter erwartet die Funktion den Dateinamen. Mögliche Typen sind fifo, char, dir, block, link, file und unknown.

Die letzte Änderung der Datei wird einmal als UNIX-Zeitwert und einmal als formatierter String gespeichert. Den Änderungszeitpunkt ermittelt die Funktion filemtime, die als Parameter den Dateinamen erwartet und den UNIX-Zeitstempel zurückgibt.

Die Funktion fileowner ermittelt den Besitzer der Datei. Leider ist diese Funktion jedoch UNIX-spezifisch, weshalb sie unter Windows keinen zuverlässigen Wert liefern wird. Als Parameter erwartet auch sie den Dateinamen und gibt den Besitzer zurück.

Die Funktionen is_readable, is_writeable und is_executable überprüfen, ob die als Parameter übergebene Datei zu lesen, zu schreiben und ausführbar ist. Der Rückgabewert der Funktionen ist entweder TRUE oder FALSE.

Die Verwendung dieser Klasse ist wesentlich übersichtlicher und einfacher, als jedes Mal die Funktionen einzeln aufzurufen. Natürlich ist bei dieser Klasse immer noch Platz für Erweiterungen oder Änderungen. Welche Sie vornehmen, bleibt Ihren eigenen Wünschen überlassen.

# Dateisystemoperationen

Gelegentlich wird es bei der Arbeit mit Dateien und Verzeichnissen vorkommen, dass Sie Dateien umbenennen, verschieben oder Verzeichnisse erstellen oder löschen möchten. Mit den entsprechenden Funktionen werde ich mich in diesem Kapitel beschäftigen.

Mit der Funktion copy können Sie eine Datei kopieren. Als ersten Parameter erwartet sie den Quelldateinamen und als zweiten Parameter den Zieldateinamen. Konnte die Datei kopiert werden, liefert die Funktion TRUE zurück, andernfalls FALSE.

bool copy(string source, string destination)

Die Funktion rename ermöglicht entweder das Umbenennen oder das Verschieben einer Datei (wenn Verzeichnisse mit angegeben werden). Als Parameter müssen die Quelle und das Ziel angegeben werden. Der Wert TRUE wird dann zurückgegeben, wenn die Operation erfolgreich durchgeführt werden konnte.

bool rename(string old, string new)

Für den Fall, dass Sie eine Datei löschen möchten, steht die Funktion unlink zur Verfügung. Sie erwartet als Parameter lediglich den Dateinamen und gibt TRUE zurück, wenn die Datei gelöscht werden konnte.

bool unlink(string filename)

Mit der Funktion mkdir können Sie ein neues Verzeichnis erstellen. Als Parameter müssen Sie lediglich den Namen für das neue Verzeichnis übergeben. Sie erhalten TRUE, wenn das Erstellen des Verzeichnisses erfolgreich war.

bool mkdir(string dirname)

Umgekehrt können Sie ein Verzeichnis mit rmdir löschen. Der Name des zu löschenden Verzeichnisses wird als Parameter übergeben. Damit das Verzeichnis gelöscht werden kann, muss es leer sein. TRUE wird zurückgegeben, wenn es gelöscht wurde.

bool rmdir(string dirname)

# Zusammenfassung

  • Die Klasse dir kann alternativ zu den Funktionen opendir, readdir und closedir verwendet werden.
  • Die Funktionen rewinddir, getcwd und chdir erweitern die Arbeitsmöglichkeiten mit Verzeichnissen.
  • Vor einem Schreib- oder Lesevorgang muss eine Datei mit fopen geöffnet und der Zugriffsmodus angegeben werden. Geschlossen wird sie mit der Funktion fclose.
  • Die Funktion fgets liest aus einer Datei.
  • Die Funktion fputs schreibt in eine Datei.
  • Die selbst definierte Klasse fileinfo ermittelt alle wichtigen Eigenschaften einer Datei.

# Fragen und Übungen

  1. Schreiben Sie ein Script, das ein Verzeichnis ausliest und dabei nur Dateien berücksichtigt.
  2. Erweitern Sie das Script aus Aufgabe 1 um die Ausgabe der Größe, des Typs und des letzten Änderungszeitpunktes der Datei.
  3. Erweitern Sie das Script um die Möglichkeit, dass der Benutzer die Datei mit einem Link öffnen kann.