With version 4.3.0, PHP‘s DOM extension has now overcome early bugs and troubles to become a useful tool for XML manipulation.

This article looks at the DOM extension and how it can be used to read and generate XML, providing new solutions for web page generation.

PHP‘s DOM XML Extension has historically been a weakest link, suffering from bugs and memory leaks plus failing to implement the XML DOM specification fully.

Recently though, things have improved. Alot. Since PHP 4.2.1 alot of work has been done to overhaul it, which should come into fruition with PHP 4.3.0.

This is good news for everyone, as it provides a powerful tool for generating practically anything with <tags /> in it.

In the past, PHP developers have been stuck with construction XML documents this way;

$xml=<<<EOD
<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<Body>
	<find_business xmlns="urn:uddi-org:api" generic="1.0" maxRows="50">
	<findQualifiers></findQualifiers>
		<name>Acme</name>
	</find_business>
</Body>
</Envelope>

EOD;

That’s fine for simple problems where the XML structure is fairly predicable. But what about something like this;

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                "DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title> An Example </title>
    <meta name="Author" content="HarryF">
  </head>
  <body>
    <table>
      <tr>
        <td>Where now?</td>
      </tr>
    </table>
  </body>
</html>

With an XHTML document, the structure can vary wildly, particularily in terms of depth of nodes. This can become a nightmare to deal with. Although tricks can be pulled using the PHP‘s SAX Expat Parser to generate XML, is SAX is essentially procedural in nature, it again lacks the flexibility we need.

Enter DOM...

Right now (pre PHP 4.3.0) it’s important to be careful to consult the manual at all time, to make sure we don’t end up using functions whcih have been depreciated and may eventually disappear. We’ll try to stick to functions that will be fully supported in future here.

So how to we create XML with DOM. A simple example;

<?php
// Create a new XML document version 1.0
$dom=domxml_new_doc('1.0');

// Create an element contain 'html'
$page=$dom->create_element('html');

// Append the element to the XML document, creating the root element
$dom->append_child($page);

echo($dom->dump_mem(true));
  • domxml_new_doc() constucts the DOM API in memory and thereby, a new XML document.
  • create_element() creates an element for manipulation and appending.
  • append_child() appends a created element to a parent node.
  • dump_mem() returns the entire XML document, as held in memory, as a string.

The result?

<?xml version="1.0"?>
<html/>

How about something more advanced? A widget we can use to create the &lt;head /&gt; element of a page;

<?php
class HeadWidget {
    var $dom;
    var $out;
    var $head;
    function HeadWidget (&$dom,$out='string') {
        $this->dom=& $dom;
        $this->out=$out;
    }
 
    function create ($myTitle,$key=-1,$desc=-1) {
        $this->head=$this->dom->create_element('head');
        $title=$this->dom->create_element('title');
        $text=$this->dom->create_text_node($myTitle);
        $title->append_child($text);
        $this->head->append_child($title);
        if ( $key!=-1 ) {
            $keywords=$this->dom->create_element('meta');
            $keywords->set_attribute('keywords',$key);
            $this->head->append_child($keywords);
        }
        if ( $desc!=-1 ) {
            $description=$this->dom->create_element('meta');
            $description->set_attribute('description',$desc);
            $this->head->append_child($description);
        }
    }
 
    function &fetch () {
        if ( $this->out=='string') {
            return $this->dom->dump_node ($this->head);
        } else {
            return $this->head;
        }
    }
}
$dom=domxml_new_doc('1.0');
$hw=new HeadWidget($dom);
$hw->create('This is a test','Testing, 123');
echo ($hw->fetch());
?>
  • create_text_node() places text in an element i.e. &amp;lt;tag&amp;gt;here is the text&amp;lt;/tag&amp;gt;
  • set_attribute() creates an XML attribute for an element i.e. &amp;lt;tag attribute=”value” /&amp;gt;
  • dump_node() returns the XML for a node as a string, something not officially in the DOM spec but very useful for creating widgets.

What the above code produces is (minus the newline characters below);

<head>
<title>This is a test</title>
<meta keywords="Testing, 123"/>
</head>

Using this approach we can build ourselves a complete widget library, allowing us to render web pages on the fly. That has an upside and a downside. It allows applications to be extremely flexible - with cunning use of OO, we can modify our applications quickly and simply, adding new “views” by just re using some existing widget classes. The downside is, generally, it takes developer to modify the look of an application and developers frequently don’t make good designers.

So how about something like this?

<?xml version="1.0"?>
<!-- poweredby.xhtml -->
<h1>Powered by <phptag id="PHP" /> and <phptag id="MySQL" /></h1>

Perhaps that’s part of an XHTML document. Notice the two “phptags”?

How about we do something with them like this;

<?php
// Define a LinkWidget class
class LinkWidget {
    var $dom;
    var $out;
    var $link;
    function LinkWidget (&$dom,$out='string') {
        $this->dom=& $dom;
        $this->out=$out;
    }
 
    function create ($href,$text) {
        $this->link=$this->dom->create_element('a');
        $this->link->set_attribute('href',$href);
        $text=$this->dom->create_text_node($text);
        $this->link->append_child($text);
    }
 
    function &fetch () {
        if ( $this->out=='string') {
            return $this->dom->dump_node ($this->link);
        } else {
            return $this->link;
        }
    }
}
 
// Load the XHTML document
$xmldocument='/path/to/file/poweredby.xhtml';
$doc=xmldocfile($xmldocument);
 
// Find all the <phptag /> 's
$phptags=$doc->get_elements_by_tagname('phptag');
 
foreach ($phptags as $phptag) {
    // Get the value of the 'id' attribute
    $id=$phptag->get_attribute('id');
    // Replace the tag with the corresponding widget
    switch ( $id ) {
        case "PHP":
            $lw=new LinkWidget($doc,'object');
            $lw->create('http://www.php.net','PHP');       
            $phptag->replace_node($lw->fetch());
            break;
        case "MySQL":
            $lw=new LinkWidget($doc,'object');
            $lw->create('http://www.mysql.com','MySQL');       
            $phptag->replace_node($lw->fetch());
            break;
    }
}
// And display...
echo ( $doc->dump_mem() );
?>

What have we got?

<?xml version="1.0"?>
<!-- poweredby.xhtml -->
<h1>Powered by <a href="http://www.php.net">PHP</a> and <a href="http://www.mysql.com">MySQL</a></h1>

A simple example perhaps but the potential of this approach makes a nice templating solution. All we need now is to use namespaces like;

<php:form />
<php:table />

But that’s another story...

Further Reading: The Singleton Pattern can be an aide in working with DOM.


develop/php_and_dom_the_way_of_the_widget.txt · Last modified: 2005/10/15 21:47