XML masih menjadi tulang punggung banyak sistem: feed RSS, berkas konfigurasi, faktur elektronik, dokumen kantor berformat Office, hingga respons API berbasis SOAP. Berbeda dengan CSV yang datanya datar, XML membangun struktur pohon bersarang dengan atribut dan namespace. PHP menyediakan tiga lapisan pembacaan: SimpleXML untuk akses cepat dan ringkas, DOMDocument untuk kendali penuh termasuk XPath, dan XMLReader untuk streaming dokumen besar. Pada Chapter ini kita akan memakai ketiganya sesuai kebutuhan, ditutup dengan penanganan galat serta aspek keamanan pembacaan XML.
Mengenal Struktur Dokumen XML #
Sebelum menulis kode, mari kita sepakati istilahnya menggunakan contoh dokumen katalog buku:
<katalog>
<buku id="1">
<judul>Belajar PHP Dasar</judul>
<penulis>Budi Santoso</penulis>
<harga>95000</harga>
</buku>
<buku id="2">
<judul>Menguasai MySQL</judul>
<penulis>Siti Rahayu</penulis>
<harga>125000</harga>
</buku>
</katalog>Elemen akar <katalog> membungkus elemen anak <buku> yang masing-masing berisi elemen cucu (<judul>, <penulis>, <harga>) serta atribut id yang tertulis di dalam tag pembuka. Hierarki inilah yang membuat XML lebih ekspresif daripada CSV — satu buku bisa membawa properti tak terbatas tanpa menambah “kolom” global.
Parsing dengan SimpleXML #
Fungsi simplexml_load_string() mengubah string XML menjadi object SimpleXMLElement yang bisa dinavigasi persis seperti struktur dokumennya:
<?php
$dokumen = <<<XML
<katalog>
<buku id="1">
<judul>Belajar PHP Dasar</judul>
<penulis>Budi Santoso</penulis>
<harga>95000</harga>
</buku>
<buku id="2">
<judul>Menguasai MySQL</judul>
<penulis>Siti Rahayu</penulis>
<harga>125000</harga>
</buku>
</katalog>
XML;
$katalog = simplexml_load_string($dokumen);
echo $katalog->buku[0]->judul . "\n";
echo $katalog->buku[1]->penulis . "\n";
echo "ID buku pertama: " . $katalog->buku[0]['id'] . "\n";
echo "Jumlah buku: " . count($katalog->buku) . "\n";Output yang Dihasilkan:
Belajar PHP Dasar
Siti Rahayu
ID buku pertama: 1
Jumlah buku: 2Mari kita telaah cara aksesnya:
- Baris setelah parsing:
$katalog->bukumengembalikan kumpulan elemen<buku>; indeks[0]dan[1]memilih buku tertentu, dancount()menghitung jumlahnya. - Akses elemen cucu: rantai
$katalog->buku[0]->judulmenelusuri jalur akar → buku pertama → judul, lalu otomatis dikonversi menjadi string saat dicetak. - Akses atribut: atribut diakses dengan kurung siku —
$katalog->buku[0]['id']— sehingga jelas dibedakan dari elemen anak. - Heredoc: string XML panjang ditulis dengan sintaks heredoc (
<<<XML ... XML;) agar kode tetap rapi.
Iterasi Elemen Anak dengan children #
Untuk memproses seluruh elemen anak dari satu node, gunakan metode children(). Contoh berikut merangkum isi pesanan pembelian:
<?php
$dokumen = <<<XML
<pesanan nomor="PO-2026-001">
<item>
<nama>Kopi Gayo</nama>
<qty>3</qty>
</item>
<item>
<nama>Teh Melati</nama>
<qty>5</qty>
</item>
<item>
<nama>Gula Aren</nama>
<qty>2</qty>
</item>
</pesanan>
XML;
$pesanan = simplexml_load_string($dokumen);
$totalQty = 0;
foreach ($pesanan->children() as $item) {
$qty = (int) $item->qty;
$totalQty += $qty;
echo sprintf("%dx %s\n", $qty, $item->nama);
}
echo "Nomor pesanan: " . $pesanan['nomor'] . "\n";
echo "Total qty: $totalQty\n";Output yang Dihasilkan:
3x Kopi Gayo
5x Teh Melati
2x Gula Aren
Nomor pesanan: PO-2026-001
Total qty: 10Perulangan foreach ($pesanan->children() as $item) menjelajah setiap elemen <item> tanpa perlu tahu jumlahnya di muka. Di dalam badan perulangan, (int) $item->qty mengonversi teks XML menjadi integer sebelum dijumlahkan — tanpa cast, operator += akan bekerja pada string dan hasil penjumlahannya salah. Atribut nomor pada elemen akar tetap terbaca dengan $pesanan['nomor'].
Namespace pada XML #
Namespace mencegah bentrok nama elemen ketika satu dokumen menggabungkan kosakata dari beberapa sistem — umum dijumpai pada feed RSS yang memakai modul Dublin Core untuk nama penulis. Elemen dengan namespace ditandai prefiks seperti dc:creator. SimpleXML tidak menampilkannya sebagai properti biasa; elemen tersebut harus diminta secara eksplisit lewat children() dengan nama prefix:
<?php
$feed = <<<XML
<rss xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<item>
<title>Panduan CSV di PHP</title>
<dc:creator>Budi Santoso</dc:creator>
</item>
<item>
<title>Streaming JSON Besar</title>
<dc:creator>Siti Rahayu</dc:creator>
</item>
</channel>
</rss>
XML;
$rss = simplexml_load_string($feed);
foreach ($rss->channel->item as $item) {
$penulis = $item->children('dc', true)->creator;
echo $item->title . " — oleh " . $penulis . "\n";
}Output yang Dihasilkan:
Panduan CSV di PHP — oleh Budi Santoso
Streaming JSON Besar — oleh Siti RahayuDeklarasi xmlns:dc="..." pada elemen akar mengikat prefiks dc ke URI namespace Dublin Core. Panggilan $item->children('dc', true) meminta semua elemen anak dalam namespace dc — argumen kedua true menandai bahwa argumen pertama adalah prefiks, bukan URI. Elemen tanpa namespace seperti <title> tetap diakses normal, sehingga dokumen campuran tetap mudah ditangani.
DOMDocument dan XPath #
SimpleXML nyaman untuk membaca, tetapi untuk kueri kompleks PHP menyediakan DOMDocument beserta DOMXPath. XPath adalah bahasa kueri untuk XML — semacam “SQL-nya dokumen terstruktur” — yang memilih node dengan kondisi berjenjang:
<?php
$dokumen = new DOMDocument();
$dokumen->loadXML(<<<'XML'
<inventaris>
<barang kategori="elektronik" stok="4">Keyboard Mekanik</barang>
<barang kategori="elektronik" stok="12">Mouse Wireless</barang>
<barang kategori="stationery" stok="30">Pulpen Gel</barang>
</inventaris>
XML);
$xpath = new DOMXPath($dokumen);
$hasil = $xpath->query("//barang[@kategori='elektronik' and @stok > 5]");
foreach ($hasil as $barang) {
echo $barang->nodeValue . " (stok: " . $barang->getAttribute('stok') . ")\n";
}Output yang Dihasilkan:
Mouse Wireless (stok: 12)Ekspresi //barang[@kategori='elektronik' and @stok > 5] berarti: cari semua elemen barang di posisi mana pun (//) yang atributnya memenuhi dua kondisi sekaligus. Hanya Mouse Wireless yang lolos karena stoknya lebih dari lima. Hasil query diiterasi sebagai node DOM; nodeValue mengambil isi teksnya dan getAttribute('stok') membaca atributnya. Perhatikan juga bahwa berkas XML dimuat dengan nowdoc (<<<'XML') — tanda kutip tunggal pada penandanya membuat isi dokumen tidak diproses sebagai string interpolasi.
| Aspek | SimpleXML | DOMDocument |
|---|---|---|
| Sintaks akses | Properti ($xml->buku) | Metode (getElementsByTagName(), XPath) |
| Kebutuhan memori | Lebih hemat | Lebih besar |
| Kueri kompleks | Terbatas | XPath penuh |
| Memodifikasi dokumen | Terbatas | Lengkap |
Penanganan Galat Parsing #
XML yang rusak — tag tidak ditutup, atribut tanpa nilai — membuat simplexml_load_string() mengembalikan false. Tanpa pengaturan tambahan, PHP juga memuntahkan banyak warning ke layar. Fungsi libxml_use_internal_errors(true) memindahkan galat tersebut ke buffer internal agar bisa kita olah dengan rapi:
<?php
libxml_use_internal_errors(true);
$dokumen = <<<XML
<daftar>
<nama>Budi</nama>
<nama>Siti
</daftar>
XML;
$hasil = simplexml_load_string($dokumen);
if ($hasil === false) {
echo "Dokumen XML tidak valid\n";
foreach (libxml_get_errors() as $galat) {
echo "- Baris " . $galat->line . ": " . trim($galat->message) . "\n";
}
libxml_clear_errors();
}Output yang Dihasilkan:
Dokumen XML tidak valid
- Baris 4: Opening and ending tag mismatch: nama line 3 and daftarElemen <nama>Siti tidak pernah ditutup sehingga parser melaporkan ketidakcocokan tag pada baris 4 — informasi nomor baris dari libxml_get_errors() mengarahkan kita langsung ke lokasi masalah. Setelah galat dibaca, libxml_clear_errors() mengosongkan buffer; tanpa pemanggilan ini, galat lama akan terbawa pada parsing berikutnya dalam request yang sama.
XMLReader untuk Dokumen Besar #
Kedua parser sebelumnya membangun seluruh dokumen di memori — tidak masalah untuk berkas kecil, tetapi menyiksa untuk dokumen ratusan megabyte. Kelas XMLReader bekerja sebagai pull parser yang maju satu node pada satu waktu dan hanya menyimpan node saat ini di memori:
<?php
$reader = new XMLReader();
$reader->open("/tmp/inventaris-besar.xml");
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT && $reader->name === "barang") {
$node = simplexml_load_string($reader->readOuterXML());
// proses satu elemen <barang> di sini
}
}
$reader->close();Metode read() memajukan kursor ke node berikutnya — elemen, teks, atribut, atau penutup — hingga dokumen habis. Ketika kursor berada di elemen <barang> yang kita incar, readOuterXML() menyalin hanya elemen tersebut sebagai string, lalu string kecil itu diparsing dengan simplexml_load_string() agar tetap nyaman diakses. Pola “streaming + parsing lokal” semacam ini akan kita perdalam pada Chapter Streaming Large Files.
Keamanan XXE #
Entitas eksternal (XML External Entity, XXE) adalah fitur XML yang memungkinkan dokumen merujuk isi dari berkas atau URL luar. Penyerang yang bisa mengirim XML ke program kita dapat memakai fitur ini untuk membaca berkas server — misalnya /etc/passwd — atau membanjiri memori melalui entitas yang mengembang.
Ringkasan #
SimpleXML memberi akses navigasi ringkas berbasis properti, children() menjelajah elemen anak termasuk yang ber-namespace, dan DOMDocument dengan XPath menghadirkan kueri berjenjang yang presisi. Galat parsing dikelola rapi melalui libxml_use_internal_errors(), dokumen besar ditangani XMLReader secara streaming, dan entitas eksternal dibiarkan mati demi keamanan. Setelah format terstruktur ini, kita beralih ke data yang lebih “liar”: baris-baris log server yang harus diparsing dengan regular expression pada Chapter berikutnya.