Abschnitt 8 · Lektion 4
CSV
CSV ist das Format, in dem Tabellen die Tabellenkalkulation verlassen: eine Zeile je Datensatz, ein
Trennzeichen zwischen den Feldern. Es sieht so einfach aus, dass fast jeder es zuerst mit explode
versucht. Das geht eine Weile gut und dann nicht mehr.
Warum explode nicht reicht
artikel,menge,preis
"Kaffee, gemahlen",2,12.45
Notizbuch,5,2.50 <?php
echo "Mit explode:\n";
foreach (file("komma.csv", FILE_IGNORE_NEW_LINES) as $zeile) {
$felder = explode(",", $zeile);
echo count($felder), " Felder: ", implode(" | ", $felder), "\n";
}
echo "\nMit fgetcsv:\n";
$datei = fopen("komma.csv", "r");
while (($felder = fgetcsv($datei, escape: "")) !== false) {
echo count($felder), " Felder: ", implode(" | ", $felder), "\n";
}
fclose($datei); Ein CSV-Feld darf das Trennzeichen enthalten. Es muss dann in Anführungszeichen stehen, und die gehören zur Notation und nicht zum Inhalt.
Im Beispiel steht "Kaffee, gemahlen" als ein Feld. explode(",") weiß davon nichts, macht
daraus zwei und liefert für diese Zeile vier Felder statt drei. Die Anführungszeichen bleiben dabei
auch noch stehen.
fgetcsv() kennt die Regel und liefert drei Felder, mit dem Komma im ersten und ohne
Anführungszeichen. Das ist der ganze Unterschied, und er ist der Grund, warum man CSV nicht selbst
zerlegt.
fgetcsv arbeitet auf einem offenen Zeiger
fgetcsv() liest eine Zeile und rückt dabei weiter. Deshalb steht davor ein fopen() und
dahinter ein fclose(), und deshalb steht der Aufruf in einer while-Schleife: Solange noch etwas
kommt, mach weiter.
Am Dateiende gibt fgetcsv false zurück, und genau darauf wird mit !== false geprüft. Der
strenge Vergleich sagt hier, was gemeint ist: Schluss ist beim Dateiende und nicht dann, wenn eine
Zeile zufällig nach nichts aussieht. Eine leere Zeile mitten in der Datei liefert nämlich ein Array
mit einem einzigen null darin, und das ist ein Ergebnis wie jedes andere. Der strenge Vergleich aus
Lektion 3.6, an einer Stelle, an der er die Absicht deutlich macht.
Das deutsche Semikolon
artikel;menge;einzelpreis
Kaffee, gemahlen;2;12,45
Schreibtisch;1;47,30
Notizbuch;5;2,50 <?php
$datei = fopen("bestellungen.csv", "r");
while (($felder = fgetcsv($datei, separator: ";", escape: "")) !== false) {
echo implode(" | ", $felder), "\n";
}
fclose($datei); In Deutschland ist das Trennzeichen fast immer ein Semikolon, und der Grund steht in jeder
Preisspalte: Bei uns ist das Komma das Dezimaltrennzeichen. Eine Datei, in der 12,45 steht und das
Komma zugleich Felder trennt, wäre nicht zu lesen.
Welches Zeichen gilt, sagst du fgetcsv mit separator. Und weil das Semikolon die Felder trennt,
darf das Komma jetzt einfach im Text stehen, ganz ohne Anführungszeichen.
Warum überall escape steht
Neben separator steht in diesem Kurs immer escape: "", und das braucht eine Erklärung, weil du
es in älteren Anleitungen nicht findest.
PHP kennt neben den Anführungszeichen noch einen zweiten Maskiermechanismus mit dem Backslash. Den
gibt es in keiner anderen CSV-Umsetzung, kein Tabellenprogramm erzeugt so etwas, und er führt dazu,
dass ein Backslash am Feldende die nächsten Zeichen verschluckt. Ein leerer Text als escape
schaltet ihn ab. Das ist die Einstellung, die du willst.
Seit PHP 8.4 verlangt PHP, dass du das Argument ausdrücklich angibst, und schreibt sonst bei jedem
Aufruf eine Deprecated-Meldung auf den Fehlerkanal. Dieser Kurs läuft auf PHP 8.5, du siehst die
Meldung also sofort, und die Prüfung deiner Challenge sieht sie auch. Wenn du sie einmal lesen
willst, ohne eine Datei umzubauen: php -r '$d = fopen("bestellungen.csv", "r"); var_dump(fgetcsv($d));'
im Terminal schreibt sie vor das Ergebnis, und das Ergebnis ist obendrein eine einzige Spalte, weil
auch separator fehlt.
Geschrieben wird das hier mit benannten Argumenten aus Lektion 6.3, sonst müsstest du zwei Parameter, die dich nicht interessieren, in der richtigen Reihenfolge mitschleppen.
Die Kopfzeile
artikel;menge;einzelpreis
Kaffee, gemahlen;2;12,45
Schreibtisch;1;47,30
Notizbuch;5;2,50 <?php
$datei = fopen("bestellungen.csv", "r");
$kopf = fgetcsv($datei, separator: ";", escape: "");
echo "Die Kopfzeile: ", implode(", ", $kopf), "\n\n";
while (($felder = fgetcsv($datei, separator: ";", escape: "")) !== false) {
$zeile = array_combine($kopf, $felder);
echo $zeile["artikel"], " kostet ", $zeile["einzelpreis"], "\n";
}
fclose($datei);
echo "\nUnd so sieht so eine Zeile aus:\n";
var_dump(array_combine($kopf, ["Tee", "1", "4,20"])); Die erste Zeile einer CSV enthält meistens die Spaltennamen. Wenn du sie einmal getrennt einliest, bevor die Schleife anfängt, hast du sie als Liste in der Hand.
Damit wird aus jeder folgenden Zeile eine Zuordnung: array_combine() nimmt die Kopfzeile als
Schlüssel und die Datenzeile als Werte. Statt $felder[2] schreibst du dann $zeile["einzelpreis"],
und in einem halben Jahr weißt du immer noch, was gemeint war.
Das setzt voraus, dass beide Listen gleich lang sind. Sind sie es nicht, wirft array_combine einen
ValueError, und das ist die richtige Reaktion: Eine Zeile mit der falschen Spaltenzahl ist kaputt.
Und im Browser steht sie als Tabelle
artikel;menge;einzelpreis
Kaffee, gemahlen;2;12,45
Schreibtisch;1;47,30
Notizbuch;5;2,50 <?php
$datei = fopen("bestellungen.csv", "r");
$kopf = fgetcsv($datei, separator: ";", escape: "");
echo "<style>";
echo "body{font-family:system-ui;padding:1rem}";
echo "table{border-collapse:collapse}";
echo "th,td{border:1px solid #999;padding:.35rem .6rem;text-align:left}";
echo "td.zahl{text-align:right}";
echo "</style>\n";
echo "<table>\n";
echo " <tr><th>", implode("</th><th>", $kopf), "</th></tr>\n";
while (($felder = fgetcsv($datei, separator: ";", escape: "")) !== false) {
echo " <tr>";
echo "<td>", $felder[0], "</td>";
echo "<td class=\"zahl\">", $felder[1], "</td>";
echo "<td class=\"zahl\">", $felder[2], " EUR</td>";
echo "</tr>\n";
}
echo "</table>\n";
fclose($datei); Dieses eine Beispiel gibt kein reines Textergebnis aus, sondern Markup, und deshalb läuft dafür ein
Server: Im Reiter Browser steht die Tabelle, die daraus wird. Tipp php index.php im
Terminal, dann siehst du dieselbe Ausgabe als Zeichen, mit <tr> und <td>.
Das ist mehr als hübsch: Tabellendaten in eine Tabelle zu verwandeln ist der Alltagsfall dieser Lektion, und ab Abschnitt 10 machst du genau das für Seiten, die ein Browser anfragt. In den anderen fünf Lektionen dieses Abschnitts steht die Ausgabe im Terminal, weil dort der Text die Aussage ist und ein Browser Leerraum zusammenfaltet.
Das <style> am Anfang muss übrigens mit ausgegeben werden. Der Server liefert hier genau eine
Datei aus, eine externe CSS-Datei daneben gibt es nicht.
Schreiben mit fputcsv
<?php
$zeilen = [
["artikel", "menge"],
["Notizbuch", 5],
["Kaffee, gemahlen", 2],
["Tee; lose", 1],
];
$datei = fopen("neu.csv", "w");
foreach ($zeilen as $zeile) {
fputcsv($datei, $zeile, separator: ";", escape: "");
}
fclose($datei);
echo "So sieht die Datei aus:\n";
echo file_get_contents("neu.csv"); fputcsv() ist das Gegenstück und nimmt dir die Regeln ab, die du beim Lesen kennengelernt hast: Es
setzt die Anführungszeichen selbst, überall dort, wo sie beim Wiedereinlesen gebraucht werden.
Im Beispiel siehst du alle drei Fälle in einer Datei. Notizbuch bleibt blank, da gibt es nichts zu
schützen. Tee; lose bekommt Anführungszeichen, weil dort das Trennzeichen im Text steht. Und
Kaffee, gemahlen bekommt ebenfalls welche, obwohl das Komma hier gar nichts trennt: PHP setzt sie
schon bei einem Leerzeichen im Feld. Das ist großzügiger als nötig und stört nicht, denn beim
Lesen fallen sie ohnehin wieder weg. Die Datei selbst zeigt dir cat neu.csv im Terminal, genau so,
wie PHP sie geschrieben hat.
Zum Mitnehmen
CSV sieht aus, als könnte man es mit explode zerlegen. Kann man nicht, und der Beweis steht gleich im ersten Beispiel. Dafür gibt es zwei Funktionen, die die Sache wirklich beherrschen.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 5 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.