Datenimport mit Silverstripe

CSV Daten performant importieren

Datenimport mit Silverstripe

Oft müssen Daten aus unterschiedliche Gründen in ein CMS importiert werden. Dies kann nur einmalig bei einer Datenmigration der Fall sein, oder es besteht die Anforderung das immer wieder neue Daten aus einem CSV importiert werden müssen. Das kann Backendseitig von einem Editor oder je nach Anwendungsfall muss dies auch für einen Webuser möglich sein.

Silverstripe CMS bietet den CSVBulkLoader an um Daten aus einem CSV in das eigene Datenmodel zu mappen und in der Datenbank zu speichern. Die Framework Funktionalität ist sehr praktisch und kann z.B. im Admin sehr einfach integriert werden um ein Admin Interface für einen Import zu erhalten. Ebenfalls kann über einen eigenen Custom-Controller eine Import Action realisiert werden. Der CSVBulkLoader basiert auf der PHP Methode fgetcsv() und mappt die Inhalte auf das konfigurierte Datenmodel.

$importer = new CsvBulkLoader('Health_Data');
$result = $importer->load($filePath);

Für grössere Datenvolumen ab 10'000 aufwärts dauert einen Import mit dem CSVBulkLoader zu lange, was Timeouts zur Folge hat. Eine Möglichkeit besteht das Timeout für den Import zu erhöhen z.B. im Script mit:

set_time_limit (60);  // Setzen der Time Limite auf 60 Sekunden

Die Erhöhung des Timeouts ist jedoch nur dann nützlich, wenn von einer bestimmten Durchlaufzeit ausgegangen werden kann. Ist die Datenmenge unterschiedlich und kann schnell mal mehr betragen führt diese Methode bald wieder in dasselbe Problem. Hinzu kommt das ein Request der mehrer Minuten dauert vom Benutzer nicht akzeptiert wird.

Eine Lösung ist direkt auf den Datenbank Layer von Silverstripe zu zugreifen und mit der LOAD DATA INFILE Syntax von MySQL einen Befehl auszuführen, welcher es erlaubt Daten aus einem Textfile in eine Table mit hoher Geschwindigkeit einzulesen. Dafür muss das CSV File in geeigneter Struktur und als File auf dem Server verfügbar sein, damit der Befehl ausgeführt werden kann. Mit der PHP tempfile() Funktion kann ein temporäres File auf dem Server angelegt werden, welches nach fclose() oder beim Scriptende wieder gelöscht wird. Mit fputcsv() kann das File als CSV bearbeitet werden.

$file = tmpfile();

$titleRow = array('type', 'sourceName', 'sourceVersion', 'unit', 'value');

fputcsv($file, $titleRow, ';');

foreach ($xmlData->Record as $record) {

    $row = array(
        $record['type'],
        $record['sourceName'],
        $record['sourceVersion'],
        $record['unit'],
        $record['value']
    );

    fputcsv($file, $row, ';');
}

fseek($file, 0);

Solange nun das File im Tempordner liegt und einen Pointer auf die Referenz zeigt können wir das File im Code wieder auslesen. Für den LOAD DATA INFILE Befehl von MySQL ist der Pfad zum File notwendig, welcher über die Metadaten des Filestreams ausgelesen werden kann.

$metaDatas = stream_get_meta_data($csvFile);
$path = $metaDatas['uri'];

In Silverstripe kann direkt über MySQL Syntax im DB Query den LOAD DATA INFILE ausgeführt werden.

DB::query("LOAD DATA LOCAL INFILE '".addslashes($path)."' REPLACE INTO TABLE Health_Data FIELDS TERMINATED BY ';' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\n' IGNORE 1 LINES (Type, SourceName, SourceVersion, Unit, Value) SET LastEdited = NOW(), Created = NOW()");