XML #
XML (eXtensible Markup Language) masih sangat relevan di dunia modern PHP — integrasi dengan sistem enterprise (SAP, Oracle), web service SOAP, RSS/Atom feed, konfigurasi (Symfony, Maven, Ant), dan berbagai format pertukaran data di industri keuangan, pemerintahan, dan logistik masih menggunakan XML secara ekstensif. PHP menyediakan tiga cara berbeda untuk bekerja dengan XML: SimpleXML yang paling mudah untuk parsing cepat, DOMDocument yang paling powerful untuk manipulasi penuh, dan XMLReader yang paling efisien untuk file besar. Mengetahui kapan menggunakan masing-masing adalah kunci.
Tiga Pendekatan XML di PHP #
flowchart TD
A{Kebutuhan XML} --> B{Ukuran file?}
B -- "Kecil-Sedang\n< beberapa MB" --> C{Perlu manipulasi\natau hanya baca?}
C -- "Hanya baca\nparse cepat" --> D[SimpleXML\nPaling mudah]
C -- "Manipulasi penuh\ntambah/edit/hapus node" --> E[DOMDocument\nPaling powerful]
B -- "Besar\n> 10MB" --> F[XMLReader\nStreaming, hemat memori]
style D fill:#dcfce7
style E fill:#dbeafe
style F fill:#fef9c3SimpleXML — Parsing Cepat #
SimpleXML mengubah XML menjadi objek PHP yang bisa diakses seperti properti dan array:
<?php
$xml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<katalog>
<produk id="1" kategori="elektronik">
<nama>Laptop Pro 14</nama>
<harga mata_uang="IDR">15000000</harga>
<stok>5</stok>
<spesifikasi>
<cpu>Intel Core i7</cpu>
<ram>16GB</ram>
<storage>512GB SSD</storage>
</spesifikasi>
<tags>
<tag>laptop</tag>
<tag>gaming</tag>
<tag>kerja</tag>
</tags>
</produk>
<produk id="2" kategori="aksesoris">
<nama>Mouse Ergo Pro</nama>
<harga mata_uang="IDR">350000</harga>
<stok>20</stok>
<spesifikasi>
<dpi>3200</dpi>
<tombol>7</tombol>
</spesifikasi>
<tags>
<tag>mouse</tag>
<tag>wireless</tag>
</tags>
</produk>
</katalog>
XML;
// Parse dari string
$katalog = simplexml_load_string($xml);
// Parse dari file
// $katalog = simplexml_load_file('katalog.xml');
// Akses elemen pertama
$produkPertama = $katalog->produk[0];
echo $produkPertama->nama; // "Laptop Pro 14"
echo $produkPertama->harga; // "15000000"
echo $produkPertama->spesifikasi->cpu; // "Intel Core i7"
// Akses atribut
echo $produkPertama['id']; // "1"
echo $produkPertama['kategori']; // "elektronik"
echo $produkPertama->harga['mata_uang']; // "IDR"
// Iterasi elemen berulang
foreach ($katalog->produk as $produk) {
$id = (string) $produk['id'];
$nama = (string) $produk->nama;
$harga = (int) $produk->harga;
$kategori = (string) $produk['kategori'];
echo "[$id] $nama — Rp " . number_format($harga) . " ($kategori)\n";
}
// Iterasi tags (elemen berulang di dalam elemen)
foreach ($katalog->produk[0]->tags->tag as $tag) {
echo "Tag: $tag\n"; // laptop, gaming, kerja
}
// PENTING: casting ke tipe PHP
// SimpleXML mengembalikan SimpleXMLElement, bukan string/int biasa
// Selalu cast saat digunakan di luar konteks string
$nama = (string) $produk->nama; // string
$harga = (int) $produk->harga; // integer
$stok = (float) $produk->stok; // float
$adaStok = ((int) $produk->stok) > 0; // boolean
SimpleXML dengan Namespace #
Namespace XML sering muncul di RSS, Atom, dan SOAP:
<?php
$rss = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:media="http://search.yahoo.com/mrss/">
<channel>
<title>Blog PHP</title>
<item>
<title>Belajar PHP 8.3</title>
<link>https://example.com/artikel/php-83</link>
<dc:creator>Budi Santoso</dc:creator>
<dc:date>2024-03-15</dc:date>
<media:thumbnail url="https://example.com/thumb.jpg" width="200" height="150"/>
</item>
</channel>
</rss>
XML;
$feed = simplexml_load_string($rss);
// Akses elemen tanpa namespace — normal
echo $feed->channel->title; // "Blog PHP"
echo $feed->channel->item->title; // "Belajar PHP 8.3"
// Akses elemen dengan namespace — butuh children() atau registerXPathNamespace
$item = $feed->channel->item;
// Cara 1: children() dengan namespace URI
$dc = $item->children('http://purl.org/dc/elements/1.1/');
$media = $item->children('http://search.yahoo.com/mrss/');
echo $dc->creator; // "Budi Santoso"
echo $dc->date; // "2024-03-15"
echo $media->thumbnail['url']; // "https://example.com/thumb.jpg"
// Cara 2: XPath dengan namespace (dibahas di bagian XPath)
Konversi SimpleXML ke Array #
<?php
function simpleXmlToArray(SimpleXMLElement $xml): array
{
$hasil = [];
foreach ($xml->attributes() as $nama => $nilai) {
$hasil['@' . $nama] = (string) $nilai;
}
foreach ($xml->children() as $tag => $anak) {
$anakArray = simpleXmlToArray($anak);
if (isset($hasil[$tag])) {
// Sudah ada — jadikan array
if (!is_array($hasil[$tag]) || !isset($hasil[$tag][0])) {
$hasil[$tag] = [$hasil[$tag]];
}
$hasil[$tag][] = $anakArray;
} else {
$hasil[$tag] = $anakArray;
}
}
// Tambahkan nilai teks jika tidak punya anak
$teks = trim((string) $xml);
if ($teks !== '' && empty($hasil)) {
return $teks;
}
if ($teks !== '') {
$hasil['@value'] = $teks;
}
return $hasil;
}
$katalog = simplexml_load_string($xml);
$array = simpleXmlToArray($katalog);
echo json_encode($array, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
XPath — Query Elemen #
XPath adalah bahasa query untuk XML — seperti SQL tapi untuk dokumen XML:
<?php
$katalog = simplexml_load_string($xmlString);
// XPath dasar
$semuaProduk = $katalog->xpath('//produk');
// Semua elemen produk di mana saja dalam dokumen
$produkElektronik = $katalog->xpath('//produk[@kategori="elektronik"]');
// Produk dengan atribut kategori="elektronik"
$namaProduk = $katalog->xpath('//produk/nama');
// Semua elemen nama di dalam produk
$hargaAtasLima = $katalog->xpath('//produk[harga > 5000000]');
// Produk dengan harga > 5 juta
// XPath dengan fungsi
$produkDenganTag = $katalog->xpath('//produk[tags/tag = "gaming"]');
// Produk yang punya tag "gaming"
$pertama = $katalog->xpath('//produk[1]'); // elemen pertama (1-based di XPath!)
$terakhir = $katalog->xpath('//produk[last()]');
// XPath mengembalikan array of SimpleXMLElement
foreach ($produkElektronik as $produk) {
echo (string) $produk->nama . "\n";
}
// XPath dengan namespace
$feed = simplexml_load_string($rssXml);
$feed->registerXPathNamespace('dc', 'http://purl.org/dc/elements/1.1/');
$feed->registerXPathNamespace('media', 'http://search.yahoo.com/mrss/');
$creators = $feed->xpath('//dc:creator');
$thumbnails = $feed->xpath('//media:thumbnail');
foreach ($creators as $creator) {
echo (string) $creator . "\n";
}
foreach ($thumbnails as $thumb) {
echo $thumb['url'] . "\n";
}
DOMDocument — Manipulasi Penuh #
DOMDocument memberikan kontrol penuh atas dokumen XML — baca, ubah, tambah, hapus node, dan generate XML baru:
<?php
// Parse XML yang sudah ada
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
// Load dari string
$dom->loadXML($xmlString);
// Load dari file
// $dom->load('katalog.xml');
// Navigasi DOM
$katalog = $dom->documentElement; // elemen root
$produk1 = $katalog->firstElementChild; // anak pertama
// getElementsByTagName — semua elemen dengan nama tertentu
$semuaNama = $dom->getElementsByTagName('nama');
foreach ($semuaNama as $nama) {
echo $nama->textContent . "\n"; // Laptop Pro 14, Mouse Ergo Pro
}
// getElementById — butuh atribut yang di-declare sebagai ID di DTD/schema
// Lebih umum: gunakan XPath
// DOMXPath untuk query
$xpath = new DOMXPath($dom);
$produk = $xpath->query('//produk[@kategori="elektronik"]');
foreach ($produk as $node) {
$nama = $xpath->query('nama', $node)->item(0)->textContent;
$harga = $xpath->query('harga', $node)->item(0)->textContent;
echo "$nama: Rp " . number_format((int)$harga) . "\n";
}
// evaluate() — XPath yang mengembalikan nilai scalar
$jumlahProduk = $xpath->evaluate('count(//produk)');
echo "Total produk: $jumlahProduk\n"; // 2
$totalHarga = $xpath->evaluate('sum(//produk/harga)');
echo "Total harga: Rp " . number_format($totalHarga) . "\n";
Memodifikasi XML #
<?php
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->loadXML($xmlString);
$xpath = new DOMXPath($dom);
// Ubah nilai elemen
$stokNode = $xpath->query('//produk[@id="1"]/stok')->item(0);
$stokNode->textContent = '3'; // ubah stok dari 5 ke 3
// Ubah atribut
$produkNode = $xpath->query('//produk[@id="1"]')->item(0);
$produkNode->setAttribute('updated', date('Y-m-d'));
// Tambah elemen baru
$produkBaru = $dom->createElement('produk');
$produkBaru->setAttribute('id', '3');
$produkBaru->setAttribute('kategori', 'furniture');
$namaBaru = $dom->createElement('nama', 'Meja Kerja Ergonomis');
$hargaBaru = $dom->createElement('harga', '2500000');
$hargaBaru->setAttribute('mata_uang', 'IDR');
$stokBaru = $dom->createElement('stok', '8');
$produkBaru->appendChild($namaBaru);
$produkBaru->appendChild($hargaBaru);
$produkBaru->appendChild($stokBaru);
$dom->documentElement->appendChild($produkBaru);
// Hapus elemen
$produkHapus = $xpath->query('//produk[@id="2"]')->item(0);
$produkHapus->parentNode->removeChild($produkHapus);
// Simpan hasil
$xmlBaru = $dom->saveXML();
$dom->save('katalog_updated.xml');
echo $xmlBaru;
Generate XML dari Scratch #
<?php
// Dengan DOMDocument
function buatKatalogXml(array $produkData): string
{
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$katalog = $dom->createElement('katalog');
$katalog->setAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
$dom->appendChild($katalog);
foreach ($produkData as $data) {
$produk = $dom->createElement('produk');
$produk->setAttribute('id', $data['id']);
$produk->setAttribute('kategori', $data['kategori']);
$nama = $dom->createElement('nama');
$nama->appendChild($dom->createTextNode($data['nama'])); // TextNode untuk escape otomatis
$produk->appendChild($nama);
$harga = $dom->createElement('harga', $data['harga']);
$harga->setAttribute('mata_uang', 'IDR');
$produk->appendChild($harga);
$stok = $dom->createElement('stok', $data['stok']);
$produk->appendChild($stok);
$katalog->appendChild($produk);
}
return $dom->saveXML();
}
// Dengan XMLWriter — lebih ringkas untuk generate XML besar
function buatXmlWriter(array $produkData): string
{
$writer = new XMLWriter();
$writer->openMemory();
$writer->setIndent(true);
$writer->setIndentString(' ');
$writer->startDocument('1.0', 'UTF-8');
$writer->startElement('katalog');
foreach ($produkData as $data) {
$writer->startElement('produk');
$writer->writeAttribute('id', $data['id']);
$writer->writeAttribute('kategori', $data['kategori']);
$writer->writeElement('nama', $data['nama']);
$writer->startElement('harga');
$writer->writeAttribute('mata_uang', 'IDR');
$writer->text($data['harga']);
$writer->endElement(); // harga
$writer->writeElement('stok', $data['stok']);
$writer->endElement(); // produk
}
$writer->endElement(); // katalog
$writer->endDocument();
return $writer->outputMemory();
}
$data = [
['id' => 1, 'nama' => 'Laptop <Pro>', 'harga' => 15000000, 'stok' => 5, 'kategori' => 'elektronik'],
['id' => 2, 'nama' => 'Mouse & Keyboard', 'harga' => 500000, 'stok' => 10, 'kategori' => 'aksesoris'],
];
echo buatXmlWriter($data);
// Karakter < dan & otomatis di-escape menjadi < dan &
XMLReader — Streaming untuk File Besar #
Ketika file XML terlalu besar untuk dimuat seluruhnya ke memori, gunakan XMLReader yang membaca satu node pada satu waktu:
<?php
// File XML dengan jutaan produk
$reader = new XMLReader();
$reader->open('produk_besar.xml'); // atau loadXML untuk string
$jumlah = 0;
$total = 0;
while ($reader->read()) {
// Hanya proses elemen pembuka bernama 'produk'
if ($reader->nodeType !== XMLReader::ELEMENT || $reader->name !== 'produk') {
continue;
}
// Konversi node saat ini ke SimpleXMLElement untuk akses mudah
$node = new SimpleXMLElement($reader->readOuterXml());
$harga = (int) $node->harga;
$total += $harga;
$jumlah++;
// Loncat ke elemen produk berikutnya (skip isi dalam produk)
$reader->next('produk');
}
$reader->close();
echo "Jumlah produk: $jumlah\n";
echo "Total harga: Rp " . number_format($total) . "\n";
// Memori yang digunakan: konstan, tidak peduli ukuran file
// XMLReader dengan namespace
$reader = new XMLReader();
$reader->open('feed_dengan_namespace.xml');
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT) {
echo "Elemen: " . $reader->name . "\n";
echo "Namespace: " . $reader->namespaceURI . "\n";
echo "Local name: " . $reader->localName . "\n";
if ($reader->hasAttributes) {
while ($reader->moveToNextAttribute()) {
echo " Atribut: " . $reader->name . " = " . $reader->value . "\n";
}
$reader->moveToElement(); // kembali ke elemen
}
}
}
Parsing RSS/Atom Feed #
<?php
function parseRssFeed(string $url): array
{
$ctx = stream_context_create([
'http' => ['timeout' => 10, 'user_agent' => 'PHP RSS Reader/1.0'],
]);
$xmlString = file_get_contents($url, false, $ctx);
if ($xmlString === false) {
throw new \RuntimeException("Gagal mengambil feed dari: $url");
}
$feed = simplexml_load_string($xmlString);
if ($feed === false) {
throw new \RuntimeException("Feed bukan XML yang valid");
}
$items = [];
// Detect format: RSS atau Atom
$isAtom = $feed->getName() === 'feed';
if ($isAtom) {
// Atom feed
foreach ($feed->entry as $entry) {
$items[] = [
'judul' => (string) $entry->title,
'link' => (string) $entry->link['href'],
'tanggal' => (string) $entry->updated,
'ringkasan' => (string) $entry->summary,
'penulis' => (string) $entry->author->name,
];
}
} else {
// RSS 2.0
$feed->registerXPathNamespace('dc', 'http://purl.org/dc/elements/1.1/');
$feed->registerXPathNamespace('content', 'http://purl.org/rss/1.0/modules/content/');
foreach ($feed->channel->item as $item) {
$dc = $item->children('http://purl.org/dc/elements/1.1/');
$items[] = [
'judul' => (string) $item->title,
'link' => (string) $item->link,
'tanggal' => (string) $item->pubDate,
'ringkasan' => strip_tags((string) $item->description),
'penulis' => isset($dc->creator) ? (string) $dc->creator : '',
];
}
}
return $items;
}
// Gunakan
$berita = parseRssFeed('https://example.com/feed.rss');
foreach ($berita as $item) {
echo "{$item['judul']}\n";
echo " {$item['link']}\n";
echo " {$item['tanggal']}\n\n";
}
Konversi Array ke XML dan Sebaliknya #
<?php
// Array ke XML
function arrayToXml(array $data, DOMDocument $dom, DOMElement $parent): void
{
foreach ($data as $kunci => $nilai) {
// Kunci numerik menjadi 'item'
$tag = is_numeric($kunci) ? 'item' : $kunci;
// Sanitasi nama tag (hapus karakter tidak valid)
$tag = preg_replace('/[^a-zA-Z0-9_\-.]/', '_', $tag);
$tag = ltrim($tag, '0123456789.-');
if (empty($tag)) $tag = 'item';
if (is_array($nilai)) {
$elemen = $dom->createElement($tag);
$parent->appendChild($elemen);
arrayToXml($nilai, $dom, $elemen);
} else {
$elemen = $dom->createElement($tag);
$elemen->appendChild($dom->createTextNode((string) $nilai));
$parent->appendChild($elemen);
}
}
}
function toXml(array $data, string $root = 'root'): string
{
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$rootElem = $dom->createElement($root);
$dom->appendChild($rootElem);
arrayToXml($data, $dom, $rootElem);
return $dom->saveXML();
}
$data = [
'user' => [
'id' => 1,
'nama' => 'Budi & Teman',
'email' => '[email protected]',
'tags' => ['php', 'mysql', 'redis'],
],
];
echo toXml($data, 'request');
/*
<?xml version="1.0" encoding="UTF-8"?>
<request>
<user>
<id>1</id>
<nama>Budi & Teman</nama>
<email>[email protected]</email>
<tags>
<item>php</item>
<item>mysql</item>
<item>redis</item>
</tags>
</user>
</request>
*/
Ringkasan #
- SimpleXML untuk parsing cepat — akses elemen seperti properti dan atribut seperti array. Selalu cast ke tipe PHP (
(string),(int),(float)) karena SimpleXMLElement bukan tipe skalar biasa.- DOMDocument untuk manipulasi penuh — tambah, ubah, hapus node; generate XML dari scratch; lebih verbose tapi lebih powerful dari SimpleXML.
- XMLWriter untuk generate XML — lebih ringkas dari DOMDocument untuk generate dokumen besar, terutama yang tidak perlu dimanipulasi setelah dibuat.
- XMLReader untuk file besar — streaming satu node pada satu waktu, memori konstan tidak peduli ukuran file. Kombinasikan
readOuterXml()+SimpleXMLElementuntuk akses mudah per node.- XPath adalah cara terbaik query elemen —
//produk[@kategori="elektronik"],count(//produk),sum(//harga). GunakanDOMXPath::evaluate()untuk ekspresi yang mengembalikan nilai scalar.- Namespace butuh handling eksplisit — di SimpleXML gunakan
children($namespaceUri)atauregisterXPathNamespace(). Di DOMXPath gunakanregisterNamespace().createTextNode()bukancreateElement('tag', $nilai)— untuk nilai yang mungkin mengandung karakter XML spesial (<,>,&), gunakancreateTextNode()agar otomatis di-escape.- Deteksi format RSS vs Atom — RSS menggunakan
<rss>sebagai root dengan<item>di dalam<channel>; Atom menggunakan<feed>sebagai root dengan<entry>.