csv- oder txt-Dateien mit den Filesystem Functions?
Onkel Hans
- php
Hallo Forum,
ich habe mich vor ca. 1 Jahr, auch mit Hilfe des Forums hier, damit beschäftigt, wie man kleinere Daten mit PHP und einer kleinen Textdatei ohne Datenbank speichern kann.
Also mit den Filesystem Functions und dem Artikel Sperren von Dateien von Christian Seiler.
Da ich damals sehr intensiv geübt habe und mehrere Beispiele programmiert habe, habe ich diese heute nach langer Zeit wieder rausgesucht, um mich mit dem Thema zu befassen.
Dabei ist mir aufgefallen, dass ich damals zum Speichern keine txt-Dateien, sondern csv-Dateien entstehen habe lassen. Ich bin mir sicher, dass das einen Grund hatte, weiß aber leider nicht mehr, welchen.
Meine Frage: In Zusammenhang mit dem Speichern von Dateien mit den Filesystem Functions - gibt es da einen Unterschied, ob man mit txt- oder csv-Dateien arbeitet bzw. hat eine der beiden Möglichkeiten einen Vorteil/Nachteil?
Mit der Bitte um Aufklärung und freundlichen Grüßen
Onkel Hans
Hi,
Meine Frage: In Zusammenhang mit dem Speichern von Dateien mit den Filesystem Functions - gibt es da einen Unterschied, ob man mit txt- oder csv-Dateien arbeitet bzw. hat eine der beiden Möglichkeiten einen Vorteil/Nachteil?
Erst mal *sind* CSV-Dateien Text-Dateien - nur mit "Text" in einem bestimmten Format.
Es gibt ein paar vorgefertigte Funktionen dafuer, wie fgetcsv/fputcsv, die es einem etwas einfacher machen, CSV-Dateien einzulesen bzw. CSV-Datensaetze wegzuschreiben.
Ob du CSV fuer deine Daten nutzen willst, oder ein anderes, selbst definiertes Format, und welche Vorteile das jeweils haette, musst du aber selber wissen.
MfG ChrisB
Hallo,
Es gibt ein paar vorgefertigte Funktionen dafuer, wie fgetcsv/fputcsv, die es einem etwas einfacher machen, CSV-Dateien einzulesen bzw. CSV-Datensaetze wegzuschreiben.
Ich habe auch mal einige Klassen zum Thema CSV geschrieben. es gibt zwar keine Doku, aber ich poste sie mal und fragen beantworte ich gerne:
Die Vorteile dieser Klasse:
Wenn du die Klassen erweiterst / neue Klassen erstellst, fände ich es schön, informiert zu werden.
abstract class Model {
protected $data;
protected $dimensions = 1;
public function __construct($dimensions) {
$this->dimensions = $dimensions;
}
public function __destruct() {}
abstract public function save();
abstract public function open();
}
abstract class Model2D extends Model {
protected $idfield = -1;
protected $rowlength = 1;
public function __construct($rowlength) {
parent::__construct(2);
$this->rowlength = $rowlength;
}
public function __destruct() {
parent::__destruct();
unset($data);
}
public function setIdfield($idfield) {
if(is_numeric($idfield)) {
if($idfield < $this->rowlength) {
$this->idfield = $idfield;
} else {
throw new Exception('idfield must be lower than rowlength');
}
} else {
throw new Exception('idfield has to be numeric');
}
}
public function getIdfield() {
return $this->idfield;
}
public function add($row) {
if($this->idfield != -1) {
if($this->idExists($row[$this->idfield])) {
throw new Exception('A row with this ID exists');
return;
}
}
if(count($row) > $this->rowlength) {
$row = array_slice($row,0,$this->rowlength);
} else if(count($row) < $this->rowlength) {
$row = array_pad($row, $this->rowlength, '');
}
$this->data[] = $row;
}
public function remove($id) {
$key = $this->findRow($id);
if($key != -1) {
unset($this->data[$key]);
}
}
public function idExists($id, $idfield = -1) {
if($idfield == -1) {
if($this->idfield != -1) {
$idfield = $this->idfield;
} else {
$idfield = 0;
}
}
foreach($this->data as $key => $row) {
if($row[$idfield] == $id) {
return true;
}
}
return false;
}
public function update($id, $data, $idfield = -1) {
if($idfield == -1) {
if($this->idfield != -1) {
$idfield = $this->idfield;
} else {
$idfield = 0;
}
}
foreach($this->data as $key => $row) {
if($row[$idfield] == $id) {
if(count($data) > $this->rowlength) {
$data = array_slice($data,0,$this->rowlength);
} else if(count($data) < $this->rowlength) {
$data = array_pad($data, $this->rowlength, '');
}
$this->data[$key] = $data;
return $key;
}
}
return -1;
}
public function findRow($id, $idfield = -1) {
if($idfield == -1) {
if($this->idfield != -1) {
$idfield = $this->idfield;
} else {
$idfield = 0;
}
}
foreach($this->data as $key => $row) {
if($row[$idfield] == $id) {
return $key;
}
}
return -1;
}
}
class CSV extends Model2D {
private $handle;
private $delimiter = ",";
private $flcaption = false;
private $fields;
public function __construct($filename, $rowlength) {
parent::__construct($rowlength);
if(file_exists($filename) && !is_dir($filename)) {
$this->handle = fopen($filename, "a+");
}
}
public function save() {
ftruncate($this->handle, 0);
rewind($this->handle);
if($this->flcaption) {
fwrite($this->handle, CSV::toCsvString($this->fields, $this->delimiter)."\r\n");
}
foreach($this->data as $row) {
fwrite($this->handle, CSV::toCsvString($row, $this->delimiter)."\r\n");
}
}
public function setDelimiter($delimiter) {
$this->delimiter = $delimiter{0};
}
public function setFirstLineCaption($flcaption) {
$this->flcaption = ($flcaption == true);
}
public function setCaptions($fields) {
if(is_array($fields)) {
$this->fields = $fields;
} else {
throw new Exception('fields has to be an array');
}
}
public function getDelimiter() {
return $this->delimiter;
}
public function getFirstLineCaption() {
return $this->flcaption;
}
public function getCaptions() {
return $this->fields;
}
public function open() {
rewind($this->handle);
if($this->flcaption) {
$line = fgets($this->handle, 65526);
$line = str_replace(chr(10), '', $line);
$line = str_replace(chr(13), '', $line);
$row = explode($this->delimiter, $line);
$this->fields = $row;
}
while(!feof($this->handle)) {
$line = fgets($this->handle, 65526);
if(trim($line) == "") continue;
$line = str_replace(chr(10), '', $line);
$line = str_replace(chr(13), '', $line);
$row = explode($this->delimiter, $line);
$this->data[] = $row;
}
}
public function set($id, $data) {
foreach($this->data as $key=>$row) {
if($row[$this->idfield] == $id) {
$this->data[$key] = $data;
return true;
}
}
return false;
}
public function __destruct() {
fclose($this->handle);
parent::__destruct();
}
public static function toCsvString($data, $delimiter) {
return implode($delimiter, $data);
}
public function asAssoziative() {
$a = array();
foreach($this->data as $key => $value) {
if($this->idfield == -1) {
$a[$key] = $this->makeFieldNamesAsKeys($value);
} else {
$a[$value[$this->idfield]] = $this->makeFieldNamesAsKeys($value);
}
}
return $a;
}
protected function makeFieldNamesAsKeys($values) {
$return = array();
foreach($values as $k => $v) {
$return[$this->fields[$k]] = $v;
}
return $return;
}
}
mfg, Flo
Hallo,
die CSV-Datei darf keinen BOM haben, dadurch habe ich schon stundenlange Debugging-sessions benötigt :)
mfg, Flo
Hallo Flo,
danke, dass Du Code veröffentlichst. Bitte fasse meine Anmerkungen als konstruktive Kritik auf, anhand derer Du Deine guten Ansätze ausbauen kannst.
Ich habe auch mal einige Klassen zum Thema CSV geschrieben. es gibt zwar keine Doku, aber ich poste sie mal und fragen beantworte ich gerne:
für einen Anwender (hier einen Programmierer, der Deine Klasse anwendet) ist die Dokumentation unverzichtbar. Unkommentierter Code, auch wenn er von ausgezeichneten Programmierern wie z.B. den Entwicklern von OpenSSH stammt, ist miserabler Code.
[code lang=php]
abstract class Model2D extends Model {
protected $idfield = -1;
statt einer hartcodierten -1 böte sich der Einsatz einer Klassenkonstanten an.
public function add($row) {
// [...]
if(count($row) > $this->rowlength) {
$row = array_slice($row,0,$this->rowlength);
Ich erwarte hier eine Exception, nicht das stillschweigende Unterschlagen von Daten.
} else if(count($row) < $this->rowlength) {
$row = array_pad($row, $this->rowlength, '');
Auch hier erwarte ich eine Exception, nicht ein Auffüllen mit Leerzeichen.
Wenn die Anzahl der Felder nicht stimmt, Pech gehabt: nette Mitteilung, wieviele Spalten erforderlich sind. Die Anwendung kann sich dann immer noch dazu entschließen, die Exception so zu behandeln, wie Du es hier tust.
[...]
class CSV extends Model2D {
private $handle;
private $delimiter = ",";
private $flcaption = false;
private $fields;
public function setDelimiter($delimiter) {
$this->delimiter = $delimiter{0};
}
Was ist, wenn der Delimiter ein Zeichen ist, das in UTF-8 o.ä. mit mehr als einem Byte codiert wird?
public function open() {
[...]
while(!feof($this->handle)) {
$line = fgets($this->handle, 65526);
if(trim($line) == "") continue;
$line = str_replace(chr(10), '', $line);
$line = str_replace(chr(13), '', $line);
Nein, natürlich nicht. Du machst die schönen Zeilenumbrüche in meinem Feld kaputt. Ich will die behalten, dafür gibt es ja ein weiteres Spezialzeichen (typischerweise doppelte Anführungszeichen). Denke an die Behandlung dieses Spezialzeichens im Feld.
$row = explode($this->delimiter, $line);
Deswegen darf das Trennzeichen bei Dir nicht im Feld vorkommen :-)
Hier noch ein Archivthread (ich steige weit hinten ein), der sich mit dem Thema CSV beschäftigte.
Ich wünsche mir,
- dass Du Deinen Code kommentierst,
- dass Du Klassenkonstanten verwendest,
- dass Feldwerte das Trennzeichen enthalten dürfen
(benutze ein Texterkennungszeichen, in fgetcsv $enclosure),
- dass Feldwerte das Zeilentrennzeichen enthalten dürfen
(auch hier schlägt das Texterkennungszeichen zu),
- dass nicht stillschweigend Daten verloren gehen können,
- dass nicht stillschweigend unvollständige Daten hinzugefügt werden können.
Freundliche Grüße
Vinzenz
PS: Was macht Dein Notebook und XP SP3?
Hallo
Meine Frage: In Zusammenhang mit dem Speichern von Dateien mit den Filesystem Functions - gibt es da einen Unterschied, ob man mit txt- oder csv-Dateien arbeitet bzw. hat eine der beiden Möglichkeiten einen Vorteil/Nachteil?
Die PHP-Funktionen für das Dateisystem enthalten auch die CSV-Funktionen, letztere gehören also zu ersteren, womit der Vorteil des definierten Formats übrigbleibt.
Tschö, Auge