0251 / 590 837 15
info@a-coding-project.de

XML und Objekte in PHP

XML ist eine Auszeichnungssprache, die in Schnittstellen oder Konfigurationsdateien zum Einsatz kommen. Sie zeichnet sich durch eine einfache Lesbarkeit und eine weite Verbreitung in vielen Programmiersprachen aus. Es bietet viele Möglichkeiten zur Validierung und dank XSLT auch eine eigene Sprache zur Darstellung von Daten.

Der ideale Einsatz für XML ist, wenn die Daten relativ komplex sind oder von Hand bearbeitet werden müssen. Für Schnittstellen ohne große Komplexität würde ich allerdings zu JSON greifen, weil hier der Overhead kleiner und die Performance damit besser ist. Für eine einfache Key-Value Übertragung ist es somit nur bedingt zu gebrauchen.

XML erstellen mit PHP

Viele erstellen XML einfach durch das zusammensetzen der Werte innerhalb PHP. Das könnte dann zum Beispiel so aussehen:

<person gender="<?= $person->gender; ?>">
	<firstname><?= $person->firstname; ?></firstname>
	<lastname><?= $person->lastname; ?></lastname>
<person>

Sieht doch auf den ersten Blick schon gar nicht so schlecht aus. Allerdings wird XML hier eher als Template benutzt, wie man es von HTML kennt. Schauen wir uns doch mal eine weitere Möglichkeit an XML aufzubauen:

<?php
	header ("Content-Type:text/xml");  
	
	$xml = new DOMDocument('1.0', 'utf-8');
	$person = $xml->createElement("person");
	$person->setAttribute("gender", "male");
	$xml->appendChild($person);
	
	$firstname = $xml->createElement("firstname","Stefan");
	$person->appendChild($firstname);
	
	$lastname = $xml->createElement("lastname","Wienströer");
	$person->appendChild($lastname);
	
	echo $xml->saveXML();
?>

Klar, dass Ganze ist jetzt um einiges länger geworden. Aber im Gegensatz zum vorherigen Beispiel werden hier alle nötigen Sonderzeichen kodiert (z.B. das &).

Seinen echten Vorteil stellt der Aufbau aber da, wenn die XML-Dateien komplexer werden. Da würde das „XML-Template“ stark anwachsen und man müsste erstmal auf die Suche gehen. Als Beispiel nehme ich mal einen Artikel, der in einem Preisvergleich steht. So könnte eine einfache Artikel-Klasse aussehen:

<?php
	class article
	{
		public $name  = null;
		public $id    = null;
		public $shops = array();
		
		public function add_shop($shop) {
			$this->shops[] = $shop;
		}
		
		public function __toString()
		{
			return $this->name;
		}
	}
?>

Um das Beispiel einfach zu gestalten habe ich jetzt mal auf Getter, Setter & Co verzichtet. Fehlt jetzt noch eine Klasse für die Artikel innerhalb der Shops. Die könnte man so machen:

<?php
	class article_in_shop
	{
		public $price;
		public $url;
	}
?>

Auch nichts besonders. Um einen Artikel zu erstellen könnte man nun folgendes tun:

<?php
	$tv = new article();
	$tv->name = "Fernseher";
	$tv->id   = 1;
	
	$amazon = new article_in_shop();
	$amazon->url = "http://www.amazon.de/";
	$amazon->price = 10;
	$tv->add_shop($amazon);
	
	$ebay = new article_in_shop();
	$ebay->url = "http://www.ebay.de/";
	$ebay->price = 12;
	$tv->add_shop($ebay);
	
	print_r($tv);
?>

Jetzt kommt das XML ins Spiel! Der Trick ist, das XML-Dokument in den einzelnen Klassen zu erstellen. So kann man später die Klassen erweitern und muss nicht noch lange die Stelle im XML herrausuchen, um die Info auch mitzugeben. Sieht dann für den Artikel so aus:

<?php
	class article
	{
		public $name  = null;
		public $id    = null;
		public $shops = array();
		
		public function add_shop($shop) {
			$this->shops[] = $shop;
		}
		
		public function __toString()
		{
			return $this->name;
		}
		
		public function to_xml(DOMDocument $document)
		{
			$res = $document->createElement("article");
			
			$res->setAttribute("name", $this->name);
			$res->setAttribute("id", $this->id);
			
			return $res;
		}
	}
?>

Als Basis brauchen wir immer das DOMDocument, weil wir daraus die einzelnen Elemente bauen. Für jeden Artikel gibt es ein dann eine neues Element im XML. Um unseren Artikel jetzt zu exportieren, muss man nun folgendes aufrufen:

<?php
	$tv = new article();
	$tv->name = "Fernseher";
	$tv->id   = 1;
	
	$amazon = new article_in_shop();
	$amazon->url = "http://www.amazon.de/";
	$amazon->price = 10;
	$tv->add_shop($amazon);
	
	$ebay = new article_in_shop();
	$ebay->url = "http://www.ebay.de/";
	$ebay->price = 12;
	$tv->add_shop($ebay);
	
	header ("Content-Type:text/xml");  
	
	$xml = new DOMDocument('1.0', 'utf-8');
	$xml->appendChild($tv->to_xml($xml));
	
	echo $xml->saveXML();
?>

Wenn man jetzt mal überlegt, dass das Erstellen des Artikels in der Regel aus der Datenbank kommt, haben wir jetzt schon etwas Ordnung ins XML gebracht. Um nun die article_in_shop-Objekte zu exportieren, braucht auch diese Klasse eine to_xml-Funktion:

<?php
	class article_in_shop
	{
		public $price = null;
		public $url   = null;
		
		public function to_xml(DOMDocument $document)
		{
			$res = $document->createElement("article_in_shop");
			
			$res->setAttribute("price", $this->price);
			$res->setAttribute("url", $this->url);
			
			return $res;
		}
	}
?>

Die Funktion können wir nun einfach aus der article-Klasse aufrufen. Der Aufruf zum Export des Artikels bleibt dabei unverändert.

<?php
	class article
	{
		public $name  = null;
		public $id    = null;
		public $shops = array();
		
		public function add_shop($shop) {
			$this->shops[] = $shop;
		}
		
		public function __toString()
		{
			return $this->name;
		}
		
		public function to_xml(DOMDocument $document)
		{
			$res = $document->createElement("article");
			
			$res->setAttribute("name", $this->name);
			$res->setAttribute("id", $this->id);
			
			foreach($this->shops as $shop)
			{
				$res->appendChild($shop->to_xml($document));
			}
			
			return $res;
		}
	}
?>

Auf gleiche Weise können wir jetzt zum Beispiel weitere Details, wie den Hersteller exportieren und müssen immer nur die Klasse bearbeiten, die wir gerade bearbeiten. Die Basis des XML-Exports bleibt immer gleich. Noch interessanter wird es, wenn man die Klassen dynamisch generiert. Dazu kommen wir aber einandermal.

Das kommt auf jeden Fall dann in unserem Beispiel als Ergebnis heraus:

<article name="Fernseher" id="1">
	<article_in_shop price="10" url="http://www.amazon.de/"/>
	<article_in_shop price="12" url="http://www.ebay.de/"/>
</article>

XPath – Der Wegweiser im XML-Dschungel!

Bevor ich XPath kannte, hat sich das Auslesen von XML-Dateien bei mir immer über hunderte von Schleifen und Bedingungen erstreckt. Doch das muss nicht sein! Als Beispiel nehme ich heute einfach mal meinen RSS-Feed:

http://feeds.feedburner.com/stevieswebsite

Geben wir uns den mit PHP aus, erhalten wir eine relativ große XML-Masse:

<?php
	$xml = simplexml_load_file("http://feeds.feedburner.com/stevieswebsite");
	print_r($xml);
?>

Jetzt kommt XPath ins Spiel. XPath gibt quasi eine Adresse für einzelne Elemente an. So werden im folgenden Code alle Artikel (channel/item) durchlaufen:

<?php
	$xml = simplexml_load_file("http://feeds.feedburner.com/stevieswebsite");
	foreach($xml->xpath("channel/item") as $article)
	{
		echo htmlentities($article->title)."<br />";
	}
?>

Ist doch recht leicht, oder? Schauen wir doch mal, wie wir das auf unsere Artikel-XML-Datei anwenden können:

<?php
	$xml = simplexml_load_file("articles.xml");
	$price = 10;
	foreach($xml->xpath("//article/article_in_shop[@price=".$price."]") as $article_in_shop)
	{
		echo $article_in_shop['url']."<br />";
	}
?>

In dem Beispiel wird nach allen Anbietern gesucht, die den Artikel für 10 Euro anbieten. Das @price ist quasi der Attributfilter. Das Auslesen des Attributes Url kann dann einfach über den Zugriff als Array erfolgen. Zurück kommt ein SimpleXML-Objekt, welches ihr aber einfach als String casten könnt.

Natürlich ist auch hier wieder ein objektorientierter Ansatz wünschenswert. Ich habe die Klasse article einfach mal um die statische Funktion from_xml erweitert:

<?php
	class article
	{
		public $name  = null;
		public $id    = null;
		public $shops = array();
		
		public function add_shop($shop) {
			$this->shops[] = $shop;
		}
		
		public function __toString()
		{
			return $this->name;
		}
		
		public function to_xml(DOMDocument $document)
		{
			$res = $document->createElement("article");
			
			$res->setAttribute("name", $this->name);
			$res->setAttribute("id", $this->id);
			
			foreach($this->shops as $shop)
			{
				$res->appendChild($shop->to_xml($document));
			}
			
			return $res;
		}
		
		public static function from_xml(SimpleXMLElement $element)
		{
			$res = new article();
			$res->name = (string) $element['name'];
			
			return $res;
		}
	}
?>

Hier wird einfach das SimpleXMLElement übergeben, ein neuer Artikel erstellt und der Name zugewiesen. Aufgerufen wird das dann so:

<?php
	$xml = simplexml_load_file("articles.xml");
	print_r(article::from_xml($xml));
?>

Fehlen noch die Article-In-Shops:

<?php
	class article_in_shop
	{
		public $price = null;
		public $url   = null;
		
		public function to_xml(DOMDocument $document)
		{
			$res = $document->createElement("article_in_shop");
			
			$res->setAttribute("price", $this->price);
			$res->setAttribute("url", $this->url);
			
			return $res;
		}
		
		public static function from_xml(SimpleXMLElement $element)
		{
			$res = new article_in_shop();
			
			$res->price = (float) $element['price'];
			$res->url = (string) $element['url'];
			
			return $res;
		}
	}
?>

Auch das muss aufgerufen werden. So wurde die article-Klasse nochmal erweitert:

<?php
	class article
	{
		public $name  = null;
		public $id    = null;
		public $shops = array();
		
		public function add_shop($shop) {
			$this->shops[] = $shop;
		}
		
		public function __toString()
		{
			return $this->name;
		}
		
		public function to_xml(DOMDocument $document)
		{
			$res = $document->createElement("article");
			
			$res->setAttribute("name", $this->name);
			$res->setAttribute("id", $this->id);
			
			foreach($this->shops as $shop)
			{
				$res->appendChild($shop->to_xml($document));
			}
			
			return $res;
		}
		
		public static function from_xml(SimpleXMLElement $element)
		{
			$res = new article();
			$res->name = (string) $element['name'];
			
			foreach($element->xpath("article_in_shop") as $item)
			{
				$res->add_shop(article_in_shop::from_xml($item));
			}
			
			return $res;
		}
	}
?>

Und schon können wir die Artikel auch wieder importieren.

Validierung

Natürlich solltet ihr beim Import und Export die Daten auch validieren. Wer als richtiger Profi an die Sache herangehen möchte, kann auf DTD zusammen mit DOMDocument::validate zurückgreifen. Das ist aber ein relativ komplexes Thema, was den Beitrag hier sprengen würde. Für den Anfang würde es einfach reichen, wenn ihr in den to_xml und from_xml Funktionen einfach schaut, ob der aktuelle EIntrag so richtig ist.

XML über SQL ansteuern

Auf meiner Recherche für diesen Artikel bin ich auf eine noch experimentelle Funktion von PHP für XML gestoßen: SDO Relational Data Access Service. Damit ist es möglich auf XML über SQL-Queries zuzugreifen und das XML-File wie eine relationale Datenbank zu behandeln. Wenn bei euch Interesse besteht, arbeite ich mich da mal ein und präsentiere meine Ergebnisse.