Navigation
 Startseite
 Fachbücher
 Anzeigenmarkt
 Forum
 Webmaster News
 Script Newsletter
 Push Meldungen
 Kontakt
 Script Installation
 Php
 Php Tutorials
 Lernpfade
 Webhoster Vergleich
 Impressum

Community-Bereich
 kostenlos Registrieren
 Anmelden
 Benutzerliste

Script Datenbank
 Script Archiv
 Script Top 20
 Screenshots
 Testberichte

Suche
 

Unsere Php Scripts
 Counter Script
 Umfrage Script
 Bilder Upload Script
 Terminverwaltung
 Simple PHP Forum
 RSS Grabber

Tools und Generatoren
 .htpasswd Generator
 md5 Generator
 base64 Generator
 Markdown to HTML
 Colorpicker
 Unix timestamp Tool
 Unit Test Generator
 TLD Liste
 Webkatalog‑Verzeichnis

Hosterplus.de
Bekommen Sie Speicherplatz (Webspace), Domains...
https://www.Hosterplus.de
Artfiles.de
Bietet Serviceorientierte...
https://www.Artfiles.de
 
 
 

PHP xml_parse(): große XML-Dateien Stück für Stück

Sie befinden sich: Home > Php Tutorial > PHP xml_parse(): große XML-...

PHP xml_parse(): große XML-Dateien Stück für Stück
Eintrag am:
10.08.2026
Hits / Besucher:
7
Sprache:
  Deutsch
Tutorial Art:
eigenes
Eingetragen von:
Merkliste:
 
Beschreibung

Ein Lieferantenkatalog von 400 MB soll jede Nacht eingelesen werden. Der erste Versuch mit SimpleXML endet nach wenigen Sekunden mit einem Speicherfehler, und das Anheben des Limits verschiebt das Problem nur bis zum nächsten größeren Katalog.

Illustration zum Tutorial: PHP xml_parse(): große XML-Dateien Stück für Stück

Im Bild wandert das Dokument als Band durch einen schmalen Leser, und der Speicherbalken darunter bleibt flach. Genau dieses Verhalten beschreibt der Text.

Was PHP xml_parse() macht

PHP xml_parse gehört zur Expat-Anbindung der Sprache und arbeitet ereignisgesteuert. Sie baut kein Dokument auf, sondern liest den Text durch und meldet jedes Ereignis an eine vorher registrierte Funktion. Die Signatur lautet xml_parse(XMLParser $parser, string $data, bool $is_final = false): int und liefert 1 bei Erfolg oder 0 bei einem Fehler.

Seit PHP 8.0 ist der Parser ein Objekt der Klasse XMLParser und keine Ressource mehr. Der früher übliche Aufruf von xml_parser_free() ist seither wirkungslos und in neuem Code überflüssig.

Die englischen Parameternamen aus dem Handbuch tauchen in Fehlermeldungen und in fremdem Code wieder auf, deshalb hier die Beschreibung im Original:

xml_parse(XMLParser $parser, string $data, bool $is_final = false): int


/* parser - an xml parser created by xml_parser_create()
data - a chunk of the xml document, not necessarily complete
is_final - true for the last chunk of the xml data
return - 1 on success, 0 on failure while parsing the xml file */

Bemerkenswert ist die Formulierung zum zweiten Parameter: Ein Block muss kein vollständiges Dokument sein. Genau darauf beruht die blockweise Verarbeitung weiter unten.

Baum oder Ereignis: wann sich der Aufwand lohnt

SimpleXML und DOM bauen aus dem Dokument einen Baum, den man danach bequem durchsuchen kann. Dafür muss das ganze Dokument in den Speicher passen. PHP xml_parse baut keinen Baum, sondern meldet jedes Element im Vorbeigehen. Deshalb ist der Speicherbedarf unabhängig von der Dateigröße.

Daraus ergibt sich eine klare Arbeitsteilung. Eine Datei von wenigen hundert Kilobyte verarbeitet man am besten mit dem Baum, weil der Code kürzer und lesbarer ausfällt. Wer damit anfangen will, findet in dem PHP XML Tutorial zum Lesen, Parsen und Erstellen den passenden Einstieg, und für das nachträgliche Ändern eines Dokuments ist PHP DOMDocument zum Parsen und Manipulieren das richtige Werkzeug. Erst wenn die Datei groß oder ihre Größe unbekannt ist, lohnt der Mehraufwand hier.

Die drei Handler von PHP xml_parse()

Registriert werden vor dem ersten Aufruf von PHP xml_parse drei Rückruffunktionen: eine für öffnende Elemente, eine für schließende und eine für den Text dazwischen. Das folgende Beispiel protokolliert nur, wer wann aufgerufen wird.

<?php

$xml = '<buch><titel>PHP</titel></buch>';

$parser = xml_parser_create('UTF-8');
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);

xml_set_element_handler(
$parser,
function ($p, string $name, array $attribute): void {
echo 'START ' . $name . PHP_EOL;
},
function ($p, string $name): void {
echo 'ENDE ' . $name . PHP_EOL;
}
);

xml_set_character_data_handler(
$parser,
function ($p, string $text): void {
echo 'TEXT "' . $text . '"' . PHP_EOL;
}
);

xml_parse($parser, $xml, true);

/* START buch
START titel
TEXT "PHP"
ENDE titel
ENDE buch */

Die Option XML_OPTION_CASE_FOLDING verdient Aufmerksamkeit. Ohne sie liefert Expat alle Elementnamen in Großbuchstaben, was jede Fallunterscheidung im Handler unnötig verkompliziert. Auf false gesetzt bleiben die Namen so, wie sie im Dokument stehen.

Warum Zeichendaten in Bruchstücken ankommen

Hier liegt die häufigste Fehlerquelle im Umgang mit PHP xml_parse. Der Handler für Zeichendaten darf denselben Textknoten mehrfach melden, besonders an Blockgrenzen und rund um Entitäten. Wer den Wert zuweist statt ihn anzuhängen, verliert alles bis auf das letzte Bruchstück.

<?php

/* Falsch: jeder Aufruf ueberschreibt den vorherigen.
Aus "Gr&uuml;ne Schr&auml;nke" wird am Ende nur "nke". */
$wert = '';
$falsch = function ($p, string $text) use (&$wert): void {
$wert = $text;
};

/* Richtig: die Bruchstuecke werden aneinandergehaengt. */
$richtig = function ($p, string $text) use (&$wert): void {
$wert .= $text;
};

Der Fehler fällt lange nicht auf, weil er bei kurzen Werten ohne Sonderzeichen nie auftritt. Erst ein Umlaut oder ein Wert, der genau auf einer Blockgrenze liegt, macht ihn sichtbar. Zurückgesetzt wird die Sammelvariable im Handler für das öffnende Element.

Mit PHP xml_parse() blockweise verarbeiten

Jetzt kommt der eigentliche Zweck von PHP xml_parse zum Tragen. Die Datei wird in Blöcken gelesen und jeder Block sofort an den Parser gegeben.

<?php

$parser = xml_parser_create('UTF-8');
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);

$aktuell = '';
$puffer = '';
$anzahl = 0;

xml_set_element_handler(
$parser,
function ($p, string $name) use (&$aktuell, &$puffer): void {
$aktuell = $name;
$puffer = '';
},
function ($p, string $name) use (&$puffer, &$anzahl): void {
if ($name === 'artikel') {
$anzahl++;
}
}
);

xml_set_character_data_handler(
$parser,
function ($p, string $text) use (&$puffer): void {
$puffer .= $text;
}
);

$fp = fopen('katalog.xml', 'rb');

while (!feof($fp)) {
$block = fread($fp, 8192);
xml_parse($parser, $block, feof($fp));
}

fclose($fp);
echo $anzahl . ' Artikel verarbeitet.';

Der dritte Parameter ist der Punkt, an dem die meisten Versuche scheitern. Solange weitere Blöcke folgen, steht dort false. Beim letzten Aufruf muss er true sein, sonst meldet der Parser das Dokument als unvollständig, obwohl alles gelesen wurde. Der Ausdruck feof($fp) leistet genau das, weil er nach dem letzten Block wahr wird.

Der Speicherbedarf bleibt dabei praktisch konstant. Gemessen an einer Datei von 190 MB liegt die Spitze bei der Baumvariante deutlich über einem Gigabyte, bei dieser Schleife im niedrigen einstelligen Megabyte-Bereich. Der Grund ist einfach: es liegt immer nur ein Block von 8 KB und der aktuelle Textpuffer im Speicher.

Fehler finden mit PHP xml_parse()

Bricht die Verarbeitung ab, hilft die bloße Information "es hat nicht geklappt" nicht weiter. Expat kennt die genaue Stelle und gibt sie auf Nachfrage heraus.

<?php

if (xml_parse($parser, $block, feof($fp)) !== 1) {
$meldung = sprintf(
'XML-Fehler %d: %s in Zeile %d, Spalte %d',
xml_get_error_code($parser),
xml_error_string(xml_get_error_code($parser)),
xml_get_current_line_number($parser),
xml_get_current_column_number($parser)
);

fclose($fp);
exit($meldung);
}

/* XML-Fehler 5: not well-formed (invalid token)
in Zeile 84213, Spalte 17 */

Mit Zeile und Spalte lässt sich die kaputte Stelle in einer 400 MB großen Datei in Sekunden finden. Ohne diese Angaben bleibt nur das Durchsuchen von Hand.

Nur bestimmte Elemente einsammeln

Selten werden alle Felder gebraucht, und PHP xml_parse zwingt zu keiner Vollständigkeit. Eine Zustandsvariable sorgt dafür, dass nur innerhalb des interessierenden Bereichs gesammelt wird und der Rest ohne Aufwand vorbeizieht.

<?php

$imArtikel = false;
$feld = '';
$satz = [];

xml_set_element_handler(
$parser,
function ($p, string $name) use (&$imArtikel, &$feld, &$satz): void {
if ($name === 'artikel') {
$imArtikel = true;
$satz = [];
}
$feld = $name;
},
function ($p, string $name) use (&$imArtikel, &$satz): void {
if ($name === 'artikel') {
$imArtikel = false;
/* hier wird ein vollstaendiger Datensatz gespeichert */
}
}
);

xml_set_character_data_handler(
$parser,
function ($p, string $text) use (&$imArtikel, &$feld, &$satz): void {
if ($imArtikel && ($feld === 'ean' || $feld === 'preis')) {
$satz[$feld] = ($satz[$feld] ?? '') . trim($text);
}
}
);

Aus dreissig Feldern pro Artikel landen so nur zwei im Speicher. Bei Millionen von Datensätzen ist das der Unterschied zwischen einem Import, der durchläuft, und einem, der nach zwanzig Minuten abbricht.

Namensräume

Dokumente aus dem Behörden- und Rechnungsumfeld arbeiten fast immer mit Namensräumen. Der gewöhnliche Parser reicht das Präfix als Teil des Elementnamens durch, was die Fallunterscheidung unnötig macht.

<?php

/* Das zweite Argument ist das Trennzeichen zwischen
Namensraum-URI und Elementname. */
$parser = xml_parser_create_ns('UTF-8', ':');

/* Der Handler bekommt jetzt Namen wie
urn:beispiel:rechnung:ID */

Damit lässt sich der Namensraum sauber vom Namen trennen, statt sich auf ein Präfix zu verlassen, das jedes Dokument anders wählen darf.

flowchart TD
  A[Datei blockweise lesen] --> B[xml_parse mit Block]
  B --> C{Ereignis}
  C -->|Start-Tag| D[Zustand merken]
  C -->|Zeichendaten| E[Text anhaengen]
  C -->|End-Tag| F[Datensatz verarbeiten]
  F --> G{Datei zu Ende}
  G -->|nein| A
  G -->|ja| H[Letzter Aufruf is_final]

Das Diagramm zeigt die Schleife, die allen Beispielen oben zugrunde liegt. Der einzige Ausstieg führt über den letzten Aufruf mit gesetztem Endekennzeichen.

xml_parse() oder XMLReader?

Beide Verfahren lösen dasselbe Speicherproblem, gehen es aber unterschiedlich an. PHP xml_parse ruft den eigenen Code auf, sobald etwas passiert, weshalb der Zustand in Variablen außerhalb der Handler mitgeführt werden muss. XMLReader dreht das um: der Code fragt den Leser, ob es weitergeht, und entscheidet selbst, wann er wohin schaut.

Für neue Projekte ist XMLReader deshalb meist die angenehmere Wahl, weil der Ablauf ohne Zustandsvariablen auskommt und leichter zu lesen bleibt. Veraltet ist die Expat-Anbindung damit nicht: sie wird weiter gepflegt, ist in jedem PHP-Bestand zu finden und schlägt sich bei sehr großen Dokumenten hervorragend. Wer vorhandenen Code pflegt, hat keinen Grund umzubauen.

Fazit

PHP xml_parse ist das Werkzeug für XML-Dateien, die nicht in den Speicher passen. Der Preis dafür sind drei Handler und eine Zustandsverwaltung, der Gewinn ist ein Speicherbedarf, der unabhängig von der Dateigröße bleibt. Wer den Zeichendaten-Handler anhängen statt zuweisen lässt und beim letzten Block das Endekennzeichen setzt, hat die beiden Fallen bereits umgangen, an denen die meisten Versuche scheitern.

 


 

Kommentare (0)

Noch keine Kommentare. Sei der Erste!

Melde dich an, um einen Kommentar zu schreiben.
Bücherregal mit drei Büchern: 'PHP 4 - Grundlagen und Profiwissen' von Hanser Verlag, 'Webdesign in a Nutshell' von O'Reilly Verlag, und 'Webgestaltung' von Galileo Computing.