PHP/Tutorials/Datenspeicherung

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

Beim Entwickeln von Webapplikationen mit PHP stellt sich irgendwann die Frage, wo und wie Daten persistent gespeichert werden können. Dabei kann es sich um Nutzereingaben, eine kleine Log-Datei oder um die Inhalte eines CMS handeln.

Speicherung von Daten in Dateien

Beim Speichern der Daten in Dateien müssen zwei grundlegende Fallstricke beachtet werden:

  1. Die Dateien müssen gesperrt werden, damit nicht zwei parallel laufende Programme gleichzeitig darauf zugreifen und die Daten somit korrumpieren.
  2. Die Daten-Dateien sollen in der Regel nicht vom Webserver ausgeliefert werden – eine Nutzerdatenbank sollte sich nicht auslesen lassen. Daher sollten die Dateien vorzugsweise außerhalb des Document-Roots liegen oder ersatzweise Zugriffsgeschützt sein. Das Speichern von Dateien außerhalb des Document-Roots ist allerdings robuster, weil es nur begrenzt von den weiteren Einstellungen des Webservers abhängig ist.
Dateien lesen und schreiben und dabei jeweils sperren
<?php
// Lesen:
$filename = '/path/to/file.txt';
$file = fopen($filename, 'r');
$data = '';

if(flock($file, LOCK_SH)) {
  // etwas mit dem Inhalt der Datei tun...
  $data = fread($file, filesize($filename));
  flock($file, LOCK_UN);
}
fclose($file);
echo $data;


// Schreiben:
$file = fopen($filename, 'w');
$data = 'Hallo Leute!'.PHP_EOL.'Das hier ist ein Text!';
if(flock($file, LOCK_EX)) {
  // etwas mit der Datei tun...
  fwrite($file, $data);
  flock($file, LOCK_UN);
}
fclose($file);
echo $data;
?>
Details zu den möglichen Modi von fopen() finden Sie in der PHP-Doku.

Speicherung eines Werts pro Datei

Geht es beispielsweise um mehrere Texte, die an verschiedenen Stellen der Seite eingebunden werden und über ein Formular bearbeitbar sein sollen – fast schon ein kleines CMS, kann pro Text jeweils eine Datei pro Text in einem bestimmten Ordner angelegt werden.

Speicherung mehrerer Werte

Soll jedoch mehr als ein Wert, beispielsweise ein Array gespeichert werden, so ist es zweckmäßig, die Daten vor dem Speichern zu serialisieren, d. h. in ein speicherbares Format gebracht werden. Da es mit serialize() und JSON bereits kompakte Formate gibt, ist die Selbstentwicklung eines Formats nicht zweckmäßig.

Beispiel-Array mit verschiedenen Datentypen
$values = [
  'ersterSchlüssel' => 'ersterWert',
  'einBoolean' => true,
  'nichts' => null,
  'ein Array im Array' => [1, 2, 3.3]
];

... mit JSON

JSON ist mit etwas Übung leicht zu lesen und von Hand zu bearbeiten. Zudem bringen viele Sprachen wie JavaScript – aus dem JSON auch ursprünglich hervorging – Parser für dieses Format mit, JSON ist somit ein ideales Austauschformat.

PHP besitzt zur Verarbeitung von JSON eine Sammlung von Funktionen, hier sei kurz die Verwendung von json_encode() sowie json_decode() gezeigt.

json_encode()
echo json_encode($values, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
json_encode() wird mittels der Option JSON_PRETTY_PRINT angewiesen, die Ausgabe der Lesbarkeit zuliebe einzurücken – im Produktivbetrieb würde man darauf verzichten, JSON_UNESCAPED_UNICODE sorgt dafür, dass nicht in ASCII enthaltene Zeichen nicht escaped werden (aus ü würde dann \u00fc), das Setzen dieser Option spart Speicherplatz und ist nicht nötig, wenn die verarbeitenden Systeme mit UTF-8 umgehen können.
{
    "ersterSchlüssel": "ersterWert",
    "einBoolean": true,
    "nichts": null,
    "ein Array im Array": [
        1,
        2,
        3.3
    ]
}
json_decode()
$json = json_encode($values);
$values_from_json = json_decode($json);

// Überprüfen, ob Dekodieren erfolgreich war:
if(json_last_error() == JSON_ERROR_NONE) {
  // hier sollte die Fehlerbehandlung erfolgen
  // (z. B. Werte verwerfen o. ä.)
  echo "json_decode() hat nicht funktioniert!";
}

// Funktioniert nicht, da in der Variable ein Objekt vom Typ „stdClass“ gespeichert ist
//echo $values_from_json['ersterSchlüssel'];
// auf Eigenschaften eines Objekts zugreifen:
echo $values_from_json->{'ersterSchlüssel'};
// alternativ in ein Array umwandeln:
$values_from_json = (array)$values_from_json;
echo $values_from_json['ersterSchlüssel'];
json_decode() liefert ein Objekt, obwohl das JSON mit einem Array erstellt wurde. Dies muss beim Zugriff beachtet werden. Beispielhaft wird nach dem Dekodieren überprüft, ob Fehler aufgetreten sind. Je nach Anwendung und welcher Fehler genau (siehe PHP-Handbuch) aufgetreten sind, muss und sollte dieser geeignet behandelt werden.

... mit serialize()

Die in PHP integrierte Serialisierungsfunktion sollte benutzt werden, wenn die Daten lediglich mit PHP weiterverarbeitet werden sollen. Hier bietet serialize() den Vorteil, dass die Daten unkompliziert wieder hergestellt werden können. Arrays bleiben Arrays und bei Objekten kann zudem die Zugehörigkeit zu Klassen gewahrt bleiben, da serialize() diese Information speichert.

Verwendung von serialize()
$serialized = serialize($values);
echo $serialized.PHP_EOL;
$unserialized = unserialize($serialized);
var_export($unserialized);
Ausgabe des obigen Codes
a:4:{s:16:"ersterSchlüssel";s:10:"ersterWert";s:10:"einBoolean";b:1;s:6:"nichts";N;s:18:"ein Array im Array";a:3:{i:0;i:1;i:1;i:2;i:2;d:3.3;}}
array (
  'ersterSchlüssel' => 'ersterWert',
  'einBoolean' => true,
  'nichts' => NULL,
  'ein Array im Array' => 
  array (
    0 => 1,
    1 => 2,
    2 => 3.3,
  ),
)
Beachten Sie: Unvertrauenswürdige Benutzereingaben sollten im Allgemeinen nicht an unserialize() übergeben, stattdessen sollte JSON als Format benutzt werden. Weitere Hinweise dazu sind in der PHP-Doku zu finden.

... in einer CSV-Datei

Die Benutzung von CSV ist eine Option, wenn man eine Art Log-Datei schreiben möchte, an die jeweils ein Eintrag als neue Zeile ergänzt wird und es so vermieden wird, die gesamte Datei einzulesen und komplett neu zu schreiben, wie man es mit JSON und serialisierten PHP-Datentrukturen machen müsste. CSV lässt sich unkompliziert mit einer Tabellenkalkulation wie Microsoft Excel oder LibreOffice Calc öffnen und weiterverarbeiten.

Ein Problem bei der Weitergabe von CSV besteht darin, dass das Format nicht einheitlich definiert ist. Einigkeit besteht darin, dass irgendein Zeichen die einzelnen Werte eines Eintrags trennt und die Einträge in einer Zeile zusammengehören. Welches Zeichen die Einträge trennt, wie es escaped wird und welche Zeichenkodierung benutzt wird, legt die Anwendung fest.

Teilnehmerliste in CSV
Name,Anmeldedatum,Anreise mit dem...
Hans Mustermann,10.12.2019,Auto
Peter Lustig,11.12.2019,Zug
Klaus Meier,30.12.2019,zu Fuß
Laura Schmidt,31.12.2019,Longboard
Hein Schlau,02.01.2020,"Rad, Auto oder sogenannter ""ÖPNV""
Die erste Zeile beschreibt den Inhalt der jeweiligen Spalten, darunter folgen die eigentlichen Daten. In der letzten Zeile wird die Verwendung des Eintragstrenners , innerhalb eines Eintrags demonstriert.

PHP bietet mit fgetcsv() und fputcsv() bequem zu benutzende Funktionen zum Lesen und Schreiben von CSV-Daten.

Schreiben und Lesen von CSV-Daten
<?php
$filename = 'file.csv';

// CSV-Daten schreiben (falls die Datei noch nicht existiert, wird sie angelegt):
$file = fopen($filename, 'a');
$data = ['Louise Müller', '05.01.2020', 'noch nicht bekannt'];
if(flock($file, LOCK_EX)) {
  fputcsv($file, $data);
  flock($file, LOCK_UN);
}
fclose($file);

// CSV-Daten auslesen und in einer HTML-Tabelle ausgeben
$file = fopen($filename, 'r');
$data = '';
echo '<table>';
if(flock($file, LOCK_SH)) {
  while(!feof($file)) {
    $line = fgetcsv($file);
    echo '<tr>';
    foreach ($line as $value) {
      echo '<td>'.htmlspecialchars($value).'</td>'.PHP_EOL;
    }
    echo '</tr>'.PHP_EOL;
  }
  flock($file, LOCK_UN);
}
fclose($file);
echo '</table>';

Das Resultat:

Name Anmeldedatum Anreise mit dem...
Hans Mustermann 10.12.2019 Auto
Peter Lustig 11.12.2019 Zug
Klaus Meier 30.12.2019 zu Fuß
Laura Schmidt 31.12.2019 Longboard
Hein Schlau 02.01.2020 Rad, Auto oder sogenannter "ÖPNV"
Louise Müller 05.01.2020 noch nicht bekannt

Speicherung in einer Datenbank

Sobald man in den Daten suchen, diese sortieren oder mehrere Tabellen verknüpfen möchte, dann wird eine Datenbank benötigt. Natürlich können diese Operationen auch bei der Speicherung in Dateien implementiert werden, nur würde hier unnötigerweise das Rad neu erfunden.

Client-Server-Anwendung

Klassische Datenbanksysteme wie MySQL, MariaDB oder PostgreSQL sind als Server konzipiert, die Webapplikation bildet den Client, der die SQL-Abfragen stellt und die erhaltenen Daten verarbeitet. Von Vorteil ist hier, dass der Datenbank-Server nicht zwingend auf dem gleichen System wie die Webapplikation laufen muss und über das Netzwerk auf ihn zugegriffen werden kann. Zudem kann die Datenbank zwischen mehreren Servern redundant verfügbar gehalten werden (Replikation) und erreicht somit potenziell eine hohe Leistung. Außerdem kann je nach Leistung der Datenbank-Servers eine große Anzahl von parallelen Anfragen verarbeitet werden.

Als Nachteil ist die zusätzlich nötige Installation und Konfiguration eines Systems zu nennen. Des Weiteren muss zum Erstellen eines Datenbank-Backups aus der Datenbank gelesen und das Backup in eine Datei geschrieben werden.

Datenbank in einer Datei

Das Datenbanksystem SQLite speichert seine Daten in einer einzigen Datei. Außerdem benötigt es keinen permanent laufenden Server, sondern liest seine Datenbank bei jedem Aufruf neu ein. Oft wird SQLite daher in Programmen zur Verwaltung von lokal gespeicherten Daten benutzt. Die SQLite-Website listet Anwendungsfälle und ihre Eigenheiten auf.

Vorteile: Auf die Daten kann mit SQL zugegriffen werden. Komplexe Operationen auf den Daten führt SQLite aus und müssen vom Programmierer nicht selbst implementiert werden. Die Webapplikation lässt sich dennoch wie bei der Verwendung von blanken Dateien als Datenspeicher einfach durch das Kopieren von Dateien umziehen. Außerdem ist es ein Leichtes, Backups von der Datenbank zu machen und diese wieder einzuspielen.

Nachteile: Bei komplexen und vielen Anfragen muss SQLite die Datenbank-Datei unter Umständen sperren, wenn beispielsweise Daten geschrieben werden sollen – es kann dann ggf. nur eine Abfrage gleichzeitig verarbeitet werden.

Beachten Sie: Die Datei, in der die Datenbank gespeichert wird, muss außerhalb des Document-Roots liegen oder Zugriffsgeschützt sein, um unberechtigtes Auslesen derselben zu verhindern.

Siehe auch