Article
Instant XML with PHP and PEAR::XML_Serializer
Web Services with PEAR::XML_Serializer
Another area where PEAR::XML_Serializer can prove valuable is in system to system or application to application data exchange. Packages like PEAR::SOAP and PEAR::XML_RPC provide implementations of the respective Web services protocols, but SOAP and XML-RPC are not the only ways to move data from A to B.
Amazon, for example, provides what is commonly referred to as a REST-ful interface to their Website (for a short overview of REST Web services see Building Web Services the REST Way). What this means is that you can access the data about the products Amazon sells using nothing more that a URL. The result you get back from a URL like this is an XML document containing the data you'd normally find wrapped up in HTML on a page like this. By exposing the data as XML, Amazon makes it very easy to parse from a remote Website and display using your own HTML. Full details can be found at amazon.com/webservices (you'll need to sign up as an associate).
Where PEAR::XML_Serializer is concerned, it's very easy to parse the XML Amazon provides and turn it into a Web page:
<?php
// Include PEAR::HTTP_Request
require_once 'HTTP/Request.php';
// Include PEAR::XML_Unserializer
require_once 'XML/Unserializer.php';
// Your Amazon associate ID
$assoc_id = 'sitepoint';
// Allow the Amazon book search keyword to be entered via the URL
if (!isset($_GET['keyword']) ||
!preg_match('/^[a-zA-Z]+$/', $_GET['keyword'])) {
$_GET['keyword'] = 'php';
}
// Build the URL to access the Amazon XML
$amazon_url = 'http://rcm.amazon.com/e/cm?t=' . $assoc_id .
'&l=st1&search=' . $_GET['keyword'] .
'&mode=books&p=102&o=1&f=xml';
// Create the HTTP_Request object, specifying the URL
$Request = &new HTTP_Request($amazon_url);
// Set proxy server as necessary
// $Request->setProxy('proxy.myisp.com', '8080', 'harryf', 'secret');
// Send the request for the feed to the remote server
$status = $Request->sendRequest();
// Check for errors
if (PEAR::isError($status)) {
die("Connection problem: " . $status->toString());
}
// Check we got an HTTP 200 status code (if not there's a problem)
if ($Request->getResponseCode() != '200') {
die("Request failed: " . $Request->getResponseCode());
}
// Get the XML from Amazon
$amazon_xml = $Request->getResponseBody();
// Create an instance of XML_Unserializer
$Unserializer = new XML_Unserializer();
// Unserialize the XML
$status = $Unserializer->unserialize($amazon_xml);
// Check for errors
if (PEAR::isError($status)) {
die($status->getMessage());
}
// Get the PHP data structure from the XML
$amazon_data = $Unserializer->getUnserializedData();
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>Amazon Search for <?php echo $_GET['keyword']; ?></title>
</head>
<body>
<h1>Amazon Search for <?php echo $_GET['keyword']; ?></h1>
<p>
<a href="?keyword=Linux">Search for Linux</a> |
<a href="?keyword=Apache">Search for Apache</a> |
<a href="?keyword=MySQL">Search for MySQL</a> |
<a href="?keyword=PHP">Search for PHP</a>
</p>
<table>
<tr>
<td>
<?php
foreach ($amazon_data['product'] as $product) {
?>
<table width="600">
<tr>
<th><?php echo nl2br(wordwrap($product['title'], 40)); ?></th>
<td rowspan="2" align="right">
<a href="<?php echo $product['tagged_url']; ?>">
<img src="<?php echo $product['small_image']; ?>"
border="0" />
</a>
</td>
</tr>
<tr>
<td>
Author: <?php echo $product['author']; ?><br />
ISBN: <?php echo $product['asin']; ?><br />
Price: <?php echo $product['our_price']; ?><br />
</td>
</tr>
</table>
<?php
}
?>
</td>
</tr>
</table>
</body>
</html>
Filename: amazon.php
The code here is essentially the same as you've seen before, at the end of Getting Started with PEAR, for parsing an RSS feed. I've used PEAR::HTTP_Request (version 1.2) as an HTTP client, to give me more detailed error reporting. The rest is simply unserializing Amazon's data.
Here's what the (somewhat crude) HTML looks like in a browser:

Of course, it doesn't stop with parsing someone else's XML. How about doing the same on your own site? Here's a simple example:
<?php
// An array simulating a database result set
$products = array(
array('code' => '000325', 'item' => 'Hamster', 'price' => 13.99),
array('code' => '005523', 'item' => 'Parrot', 'price' => 76.99),
array('code' => '000153', 'item' => 'Snake', 'price' => 49.99),
);
// If ?mime=xml is in the URL, display XML
if (isset($_GET['mime']) && $_GET['mime'] == 'xml') {
error_reporting(E_ALL ^ E_NOTICE);
require_once 'XML/Serializer.php'; // Lazy include
$serializer_options = array (
'addDecl' => TRUE,
'encoding' => 'ISO-8859-1',
'indent' => ' ',
'rootName' => 'products',
'defaultTagName' => 'product',
);
$Serializer = &new XML_Serializer($serializer_options);
$status = $Serializer->serialize($products);
if (PEAR::isError($status)) {
die($status->getMessage());
}
// Display the XML
header('Content-type: text/xml');
echo $Serializer->getSerializedData();
} else {
// Otherwise the HTML equivalent
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>Product Catalog</title>
</head>
<body>
<h1>Product Catalog</h1>
<table>
<tr>
<th>Product Code</th>
<th>Item</th>
<th>Price</th>
</tr>
<?php
foreach ($products as $product) {
?>
<tr>
<td><?php echo $product['code']; ?></td>
<td><?php echo $product['item']; ?></td>
<td><?php echo $product['price']; ?></td>
</tr>
<?php
}
?>
</table>
</body>
</html>
<?php
}
?>
Filename: products.php
If someone adds ?mime=xml to the URL used to view this script, instead of receiving the page as HTML, they get the following XML:
<?xml version="1.0" encoding="ISO-8859-1"?>
<products>
<product>
<code>000325</code>
<item>Hamster</item>
<price>13.99</price>
</product>
<product>
<code>005523</code>
<item>Parrot</item>
<price>76.99</price>
</product>
<product>
<code>000153</code>
<item>Snake</item>
<price>49.99</price>
</product>
</products>
This makes it very easy to display your data on a remote affiliate Website, should you so desire. So long as you keep the code that deals with accessing and manipulating data separate from the code that deals with presenting it to an end user, it should be no problem to provide an "alternate XML view" using XML_Serializer.
Throw into the mix a library like JOX, which provides a similar XML serializer for Java Beans, and you've got a convenient mechanism for getting PHP and Java talking.
Wrap Up
As you've seen in this article, PEAR::XML_Serializer provides a very handy tool for working with XML. You've seen how to use PEAR::XML_Serializer, and now have some idea of the types of problems to which it's suited. There are still a few minor glitches to be ironed out (the version 0.9.1 used here is beta status) but, in general, PEAR::XML_Serializer performs reliably and I've yet to find any show-stopping bugs.
Most importantly, PEAR::XML_Serializer provides an approach to parsing XML that saves you from messing with XML's details. As described in A Survey of APIs and Techniques for Processing XML, PEAR::XML_Serializer provides an "Object to XML Mapping API". Although there are limitations using to this approach, for solving the types of problems you've seen in this article, an Object to XML Mapping API makes life a lot easier.
With PHP5 packing vastly improved XML support, with support for XML Schema and Relax NG, new doors may open to PEAR::XML_Serializer for handling what it currently achieves with "typeHints". And with that come further possibilities of interop with Java (via JAXB) and .NET (via it's XmlSerializer).