Advanced XML and Web Services in PHP

Download Report

Transcript Advanced XML and Web Services in PHP

Advanced XML and
Web Services
September 12, 2006
Robert Richards
[email protected]
http://www.cdatazone.org/files/workshop.zip
Agenda









Introduction to Terms and Concepts
Libxml
DOM
SimpleXML
SAX (ext/xml)
XMLReader
XSL
XMLWriter
SOAP (ext/soap)
XML Namespaces



An XML Namespace is a collection of names identified
by a URI.
They are applicable to elements and attributes.
Namespaces may or may not be associated with a
prefix.




xmlns:rob="urn:rob"
xmlns=http://www.example.com/rob
Attributes never reside within a default namespace.
It is illegal to have two attributes with the same
localname and same namespace on the same element.
XML Namespace Example
<order num="1001">
<shipping>
<name type="care_of">John Smith</name>
<address>123 Here</address>
</shipping>
<billing>
<name type="legal">Jane Doe</name>
<address>456 Somewhere else</address>
</billing>
</order>
XML Namespace Example
<order num="1001" xmlns="urn:order"
xmlns:ship="urn:shipping"
xmlns:bill="urn:billing">
<ship:shipping>
<ship:name type="care_of">John Smith</ship:name>
<ship:address>123 Here</ship:address>
</ship:shipping>
<bill:billing>
<bill:name type="legal">Jane Doe</bill:name>
<bill:address>456 Somewhere else</bill:address>
</bill:billing>
</order>
Illegal Namespace Usage
<order num="1001" xmlns="urn:order"
xmlns:order="urn:order"
xmlns:ship="urn:order">
<shipping ship:type="fed_ex" type="fed_ex">
<name ship:type="care_of"
order:type="legal">John Smith</ship:name>
</ship:shipping>
</order>
Illegal Namespace Usage
<order num="1001" xmlns="urn:order"
xmlns:order="urn:order"
xmlns:ship="urn:order">
<shipping ship:type="fed_ex" type="fed_ex">
<name ship:type="care_of"
order:type="legal">John Smith</ship:name>
</ship:shipping>
</order>
<!-- attributes on shipping element are valid ! -->
Reserved Namespaces and
Prefixes



The prefix xml is bound to
http://www.w3.org/XML/1998/namespace.
The prefix xmlns is bound to
http://www.w3.org/2000/xmlns/.
Prefixes should also not begin with the
characters xml.
Schemas and Validation


Validation insures an XML document conforms
to a set of defined rules.
Multiple mechanisms exist to write document
rule sets:
Document Type Definition (DTD)
 XML Schema
 RelaxNG

Document Type Definition (DTD)
validation/courses-dtd.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE courses [
<!ELEMENT courses (course+)>
<!ELEMENT course (title, description, credits, lastmodified)>
<!ATTLIST course cid ID #REQUIRED>
<!ELEMENT title (#PCDATA)>
<!ELEMENT description (#PCDATA)>
<!ELEMENT credits (#PCDATA)>
<!ELEMENT lastmodified (#PCDATA)>
]>
<courses>
<course cid="c1">
<title>Basic Languages</title>
<description>Introduction to Languages</description>
<credits>1.5</credits>
<lastmodified>2004-09-01T11:13:01</lastmodified>
</course>
<course cid="c2">
...
</course>
</courses>
DTD and IDs
validation/course-id.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE courses [
<!ATTLIST course cid ID #REQUIRED>
]>
<courses>
<course cid="c1">
<title xml:id="t1">Basic Languages</title>
<description>Introduction to Languages</description>
</course>
<course cid="c2">
<title xml:id="t3">French I</title>
<description>Introduction to French</description>
</course>
<course cid="c3">
<title xml:id="t3">French II</title>
<description>Intermediate French</description>
</course>
</courses>
XML Schema
validation/course.xsd
<?xml version="1.0"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="courses">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="course" minOccurs="1" maxOccurs="unbounded">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="title" type="xsd:string"/>
<xsd:element name="description" type="xsd:string"/>
<xsd:element name="credits" type="xsd:decimal"/>
<xsd:element name="lastmodified" type="xsd:dateTime"/>
</xsd:sequence>
<xsd:attribute name="cid" type="xsd:ID"/>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
RelaxNG
validation/course.rng
<grammar xmlns="http://relaxng.org/ns/structure/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<start>
<element name="courses">
<zeroOrMore>
<element name="course">
<attribute name="cid"><data type="ID"/></attribute>
<element name="title"><text/></element>
<element name="description"><text/></element>
<element name="credits"><data type="decimal"/></element>
<element name="lastmodified"><data type="dateTime"/></element>
</element>
</zeroOrMore>
</element>
</start>
</grammar>
XPath





Language to locate and retrieve information
from an XML document
A foundation for XSLT
An XML document is a tree containing nodes
The XML document is the root node
Locations are addressable similar to the syntax
for a filesystem
XPath Reference Document
xpath/courses.xml
<courses xmlns:t="http://www.example.com/title">
<course xml:id="c1">
<t:title>Basic Languages</t:title>
<description>Introduction to Languages</description>
</course>
<course xml:id="c2">
<t:title>French I</t:title>
<description>Introduction to French</description>
</course>
<course xml:id="c3">
<t:title>French II</t:title>
<description>Intermediate French</description>
<pre-requisite cref="c2" />
<?phpx A PI Node ?>
<defns xmlns="urn:default">content</defns>
</course>
</courses>
XPath Location Example
xpath/location.php
Expression:
/courses/course/description
//description
/courses/*/description
//description[ancestor::course]
Resulting Nodset:
<description>Introduction to Languages</description>
<description>Introduction to French</description>
<description>Intermediate French</description>
XPath Function Example
xpath/function.php
string(/courses/course/pre-requisite[@cref="c2"]/..)
French II
Intermediate French
content
XPath and Namespaces
xpath/namespaces.php
//title
Empty NodeSet
//t:title
<t:title>Basic Languages</t:title>
<t:title>French I</t:title>
<t:title>French II</t:title>
//defns
Empty NodeSet
//*[local-name()="defns"]
<defns xmlns="urn:default">content</defns>
PHP and XML




PHP 5 introduced numerous interfaces for
working with XML
The libxml2 library (http://www.xmlsoft.org/)
was chosen to provide XML support
The sister library libxslt provides XSLT support
I/O is handled via PHP streams
XML Entensions for PHP 5









ext/libxml
ext/xml (SAX push parser)
ext/dom
ext/simplexml
ext/xmlreader (pull parser)
ext/xmlwriter
ext/xsl
ext/wddx
ext/soap
Libxml




Contains common functionality shared across
extensions.
Defines constants to modify parse time
behavior.
Provides access to streams context.
Allows modification of error handling behavior
for XML based extensions.
Libxml: Parser Options
LIBXML_NOENT
Substitute entities with replacement content
LIBXML_DTDLOAD
Load subsets but do not perform validation
LIBXML_DTDATTR
Create defaulted attributes defined in DTD
LIBXML_DTDVALID
Loads subsets and perform validation
LIBXML_NOERROR
Suppress parsing errors from libxml2
LIBXML_NOWARNING
Suppress parser warnings from libxml2
LIBXML_NOBLANKS
Remove insignificant whitespace on parsing
LIBXML_XINCLUDE
Perform XIncludes during parsing
LIBXML_NOCDATA
Merge CDATA nodes in Text nodes
LIBXML_NONET
Disable network access when loading
Libxml: Error Handling
bool libxml_use_internal_errors ([bool use_errors])
void libxml_clear_errors ( void )
LibXMLError libxml_get_last_error ( void )
array libxml_get_errors ( void )
Libxml: LibXMLError
Class: LibXMLError
Properties (Read-Only):
(int) level
(int) code
(int) column
(string) message
(string) file
(int) line
LibXMLError::code Values:
LIBXML_ERR_NONE
LIBXML_ERR_WARNING
LIBXML_ERR_ERROR
LIBXML_ERR_FATAL
LibXMLError Example
libxml/error.php
<?php
/* Regular Error Handling */
$dom = new DOMDocument();
$dom->loadXML('<root>');
/* New Error Handling */
libxml_use_internal_errors(TRUE);
if (! $dom->loadXML('root')) {
$arrError = libxml_get_errors();
foreach ($arrError AS $xmlError) {
var_dump($xmlError);
}
} else {
print "Document Loaded";
}
?>
LibXMLError Result
PHP Warning: DOMDocument::loadXML(): Premature end of data in tag root line 1 in
Entity, line: 1 in /home/rrichards/workshop/libxml/error.php on line 4
Warning: DOMDocument::loadXML(): Premature end of data in tag root line 1 in Entity,
line: 1 in /home/rrichards/workshop/libxml/error.php on line 4
New Error Handling:
object(LibXMLError)#2 (6) {
["level"]=> int(3)
["code"]=> int(4)
["column"]=> int(1)
["message"]=> string(34) "Start tag expected, '<' not found"
["file"]=> string(0) ""
["line"]=> int(1)
}
DOM








Tree based parser
Allows for creation and editing of XML documents
W3C Specification with DOM Level 2/3 compliancy
Provides XPath support
Provides XInclude Support
Ability to work with HTML documents
Zero copy interoperability with SimpleXML
Replacement for ext/domxml from PHP 4
DOMNode Classes







DOMDocument
DOMElement
DOMAttr
DOMComment
DOMDocumentType
DOMNotation
DOMEntity







DOMEntityReference
DOMProcessingInstruction
DOMNameSpaceNode
DOMDocumentFragment
DOMCharacterData
DOMText
DOMCdataSection
Additional DOM Classes





DOMException
DOMImplementation
DOMNodeList
DOMNamedNodeMap
DOMXPath
DOM: Sample Document
<courses>
<course cid="c1">
<title>Basic Languages</title>
<description>Introduction to Languages</description>
<credits>1.5</credits>
<lastmodified>2004-09-01T11:13:01</lastmodified>
</course>
<course cid="c2">
<title>French I</title>
<description>Introduction to French</description>
<credits>3.0</credits>
<lastmodified>2005-06-01T14:21:37</lastmodified>
</course>
<course cid="c3">
<title>French II</title>
<description>Intermediate French</description>
<credits>3.0</credits>
<lastmodified>2005-03-12T15:45:44</lastmodified>
</course>
</courses>
DOM: Document Navigation
dom/navigate.php
/* Find first description element in subtrees */
function locateDescription($nodeset) {
foreach ($nodeset AS $node) {
if ($node->nodeType == XML_ELEMENT_NODE && $node->nodeName == 'description')
{
$GLOBALS['arNodeSet'][] = $node;
return;
}
if ($node->hasChildNodes()) { locateDescription($node->childNodes); }
}
}
$dom = new DOMDocument();
$dom->load('course.xml');
$root = $dom->documentElement;
$arNodeSet = array();
if ($root->hasChildNodes()) { locateDescription($root->childNodes); }
foreach ($arNodeSet AS $key=>$node) {
print "#$key: ".$node->nodeValue."\n"; }
DOM: Document Navigation
Results
#0: Introduction to Languages
#1: Introduction to French
#2: Intermediate French
DOM:Document Navigation #2
dom/navigate-2.php
<?php
$dom = new DOMDocument();
$dom->load('course.xml');
$nodelist = $dom->getElementsByTagName('description');
foreach ($nodelist AS $key=>$node) {
print "#$key: ".$node->nodeValue."\n";
}
?>
Results:
#0: Introduction to Languages
#1: Introduction to French
#2: Intermediate French
DOM: Navigation Optimized
dom/navigate-optimized.php
function locateDescription($node) {
while($node) {
if ($node->nodeType == XML_ELEMENT_NODE && $node->nodeName == 'description') {
$GLOBALS['arNodeSet'][] = $node;
return;
}
locateDescription($node->firstChild);
$node = $node->nextSibling;
}
}
$dom = new DOMDocument();
$dom->load('course.xml');
$root = $dom->documentElement;
$arNodeSet = array();
locateDescription($root->firstChild);
foreach ($arNodeSet AS $key=>$node) {
print "#$key: ".$node->nodeValue."\n";
}
DOM: Creating a Simple Tree
dom/create_simple_tree.php
$doc = new DOMDocument();
$root = $doc->createElement("tree");
$doc->appendChild($root);
$root->setAttribute("att1", "att1 value");
$attr2 = $doc->createAttribute("att2");
$attr2->appendChild($doc->createTextNode("att2 value"));
$root->setAttributeNode($attr2);
$child = $root->appendChild($doc->createElement("child"));
$comment = $doc->createComment("My first Document");
$doc->insertBefore($comment, $root);
$pi = $doc->createProcessingInstruction("php", 'echo "Hello World!"');
$root->appendChild($pi);
$cdata = $doc->createCdataSection("special chars: & < > '");
$child->appendChild($cdata);
DOM: Simple Tree Output
<?xml version="1.0"?>
<!--My first Document-->
<tree att1="att1 value" att2="att2 value">
<child><![CDATA[special chars: & < > ']]></child>
<?php echo "Hello World!"?>
</tree>
DOM: Creating an Atom Feed
dom/atom_feed_creation.php
define('ATOMNS', 'http://www.w3.org/2005/Atom');
$feed_title = "Example Atom Feed";
$alt_url = "http://www.example.org/";
$feed = "http://www.example.org/atom/";
$doc = new DOMDocument("1.0", "UTF-8");
function create_append_Atom_elements($doc, $name, $value=NULL, $parent=NULL) {
if ($value)
$newelem = $doc->createElementNS(ATOMNS, $name, $value);
else
$newelem = $doc->createElementNS(ATOMNS, $name);
if ($parent) { return $parent->appendChild($newelem); }
}
$feed = create_append_Atom_elements($doc, 'feed', NULL, $doc);
create_append_Atom_elements($doc, 'title', $feed_title, $feed);
create_append_Atom_elements($doc, 'subtitle', $feed_title, $feed);
create_append_Atom_elements($doc, 'id', $alt_url, $feed);
create_append_Atom_elements($doc, 'updated', date('c'), $feed);
$doc->formatOutput = TRUE;
print $doc->saveXML();
DOM: Creating an Atom Feed
Result (initial structure)
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Example Atom Feed</title>
<subtitle>Example Atom Feed</subtitle>
<id>http://www.example.org/</id>
<updated>2006-03-23T01:39:40-05:00</updated>
</feed>
DOM: Creating an Atom Feed
dom/atom_feed_creation.php
$entry = create_append_Atom_elements($doc, 'entry', NULL, $feed);
$title = create_append_Atom_elements($doc, 'title', 'My first entry', $entry);
$title->setAttribute('type', 'text');
$link = create_append_Atom_elements($doc, 'link', NULL, $entry);
$link->setAttribute('type', 'text/html');
$link->setAttribute('rel', 'alternate');
$link->setAttribute('href', 'http://www.example.org/entry-url');
$link->setAttribute('title', 'My first entry');
$author = create_append_Atom_elements($doc, 'author', NULL, $entry);
create_append_Atom_elements($doc, 'name', 'Rob', $author);
create_append_Atom_elements($doc, 'id', 'http://www.example.org/entry-guid', $entry);
create_append_Atom_elements($doc, 'updated', date('c'), $entry);
create_append_Atom_elements($doc, 'published', date('c'), $entry);
$content = create_append_Atom_elements($doc, 'content', NULL, $entry);
$cdata = $doc->createCDATASection('This is my first Atom entry!<br />More to follow');
$content->appendChild($cdata);
$doc->formatOutput = TRUE;
print $doc->saveXML();
DOM: Creating an Atom Feed
Result
dom/atomoutput.xml
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Example Atom Feed</title>
<subtitle>Example Atom Feed</subtitle>
<id>http://www.example.org/</id>
<updated>2006-03-23T01:53:59-05:00</updated>
<entry>
<title type="text">My first entry</title>
<link type="text/html" rel="alternate"
href="http://www.example.org/entry-url" title="My first entry"/>
<author>
<name>Rob</name>
</author>
<id>http://www.example.org/entry-guid</id>
<updated>2006-03-23T01:53:59-05:00</updated>
<published>2006-03-23T01:53:59-05:00</published>
<content><![CDATA[This is my first Atom entry!<br />More to
follow]]></content>
</entry>
</feed>
DOM: Document Editing
dom/editing.php
$dom->load('atomoutput.xml');
$child = $dom->documentElement->firstChild;
while($child && $child->nodeName != "entry") {
$child = $child->nextSibling; }
if ($child && ($child = $child->firstChild)) {
while($child && $child->nodeName != "title") {
$child = $child->nextSibling;
if ($child) {
$child->setAttribute('type', 'html');
$text = $child->firstChild;
$text->nodeValue = "<em>My first entry</em>";
}
while($child) {
if ($child->nodeName == "updated") {
$text = $child->firstChild;
$text->nodeValue = date('c');
break;
}
$child = $child->nextSibling;
}
}
print $dom->saveXML();
}
DOM: Editing
dom/new_atomoutput.xml
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Example Atom Feed</title>
<subtitle>Example Atom Feed</subtitle>
<id>http://www.example.org/</id>
<updated>2006-03-23T01:53:59-05:00</updated>
<entry>
<title type="html">&lt;em&gt;My first entry&lt;/em&gt;</title>
<link type="text/html" rel="alternate"
href="http://www.example.org/entry-url" title="My first entry"/>
<author>
<name>Rob</name>
</author>
<id>http://www.example.org/entry-guid</id>
<updated>2006-03-23T02:29:22-05:00</updated>
<published>2006-03-23T01:53:59-05:00</published>
<content><![CDATA[This is my first Atom entry!<br />More to
follow]]></content>
</entry>
</feed>
DOM: Document Modification
dom/modify.php
/* Assume $entry refers to the first entry
element within the Atom document */
while ($entry->hasChildNodes()) {
$entry->removeChild($entry->firstChild);
}
OR
$node = $entry->lastChild;
while($node) {
$prev = $node->previousSibling;
$entry->removeChild($node);
$node = $prev;
}
/* This Will Not Work! */
foreach($entry->childNodes AS $node) {
$entry->removeChild($node);
}
/* These will work */
$children = $entry->childNodes;
$length = $children->length - 1;
for ($x=$length; $x >=0; $x--) {
$entry->removeChild($children->item($x));
}
OR
$elem = $entry->cloneNode(FALSE);
$entry->parentNode->replaceChild($elem,
$entry);
DOM and Namespaces
<xsd:complexType
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
name="ArrayOfint">
<xsd:complexContent>
<xsd:restriction base="soapenc:Array">
<xsd:attribute ref="soapenc:arrayType"
wsdl:arrayType="xsd:int[ ]"/>
</xsd:restriction>
</xsd:complexContent>
</xsd:complexType>
Dom and Namepsaces
dom/namespace.php
define("SCHEMA_NS", "http://www.w3.org/2001/XMLSchema");
define("WSDL_NS", "http://schemas.xmlsoap.org/wsdl/");
$dom = new DOMDocument();
$root = $dom->createElementNS(SCHEMA_NS, "xsd:complexType");
$dom->appendChild($root);
$root->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:wsdl", WSDL_NS);
$root->setAttribute("name", "ArrayOfint");
$content = $root->appendChild(new DOMElement("xsd:complexContent", NULL, SCHEMA_NS));
$restriction = $content->appendChild(new DOMElement("xsd:restriction", NULL, SCHEMA_NS));
$restriction->setAttribute("base", "soapenc:Array");
$attribute = $restriction->appendChild(new DOMElement("xsd:attribute", NULL, SCHEMA_NS));
$attribute->setAttribute("ref", "soapenc:arrayType");
$attribute->setAttributeNS(WSDL_NS, "wsdl:arrayType", "xsd:int[]");
DOM and Xpath
dom/xpath/dom-xpath.xml
<store>
<books>
<rare>
<book qty="4">
<name>Cannery Row</name>
<price>400.00</price>
<edition>1</edition>
</book>
</rare>
<classics>
<book qty="25">
<name>Grapes of Wrath</name>
<price>12.99</price>
</book>
<book qty="25">
<name>Of Mice and Men</name>
<price>9.99</price>
</book>
</classics>
</books>
</store>
DOM and Xpath
dom/xpath/dom-xpath.php
$doc = new DOMDocument();
$doc->load('dom-xpath.xml');
$xpath = new DOMXPath($doc);
$nodelist = $xpath->query("//name");
print "Last Book Title: ".$nodelist->item($nodelist->length - 1)->textContent."\n";
$nodelist = $xpath->query("//name[ancestor::rare]");
print "Last Rare Book Title: ".$nodelist->item($nodelist->length - 1)->nodeValue."\n";
$inventory = $xpath->evaluate("sum(//book/@qty)");
print "Total Books: ".$inventory."\n";
$inventory = $xpath->evaluate("sum(//classics/book/@qty)");
print "Total Classic Books: ".$inventory."\n";
$inventory = $xpath->evaluate("count(//book[parent::classics])");
print "Distinct Classic Book Titles: ".$inventory."\n";
DOM and Xpath Results
/* $nodelist = $xpath->query("//name")
$nodelist->item($nodelist->length - 1)->textContent */
Last Book Title: Of Mice and Men
/* $xpath->query("//name[ancestor::rare]");
$nodelist->item($nodelist->length - 1)->nodeValue */
Last Rare Book Title: Cannery Row
/* $xpath->evaluate("sum(//book/@qty)") */
Total Books: 54
/* $xpath->evaluate("sum(//classics/book/@qty)") */
Total Classic Books: 50
/* $xpath->evaluate("count(//book[parent::classics])") */
Distinct Classic Book Titles: 2
DOM and Xpath w/Namespaces
dom/xpath/dom-xpathns.xml
<store xmlns="http://www.example.com/store" xmlns:bk="http://www.example.com/book">
<books>
<rare>
<bk:book qty="4">
<bk:name>Cannery Row</bk:name>
<bk:price>400.00</bk:price>
<bk:edition>1</bk:edition>
</bk:book>
</rare>
<classics>
<bk:book qty="25">
<bk:name>Grapes of Wrath</bk:name>
<bk:price>12.99</bk:price>
</bk:book>
<bk:book qty="25" xmlns:bk="http://www.example.com/classicbook">
<bk:name>Of Mice and Men</bk:name>
<bk:price>9.99</bk:price>
</bk:book>
</classics>
<classics xmlns="http://www.example.com/ExteralClassics">
<book qty="33">
<name>To Kill a Mockingbird</name>
<price>10.99</price>
</book>
</classics>
</books>
</store>
DOM and Xpath w/Namespaces
dom/xpath/dom-xpathns.php
$nodelist = $xpath->query("//name");
print "Last Book Title: ".$nodelist->item($nodelist->length - 1)->textContent."\n";
// Last Book Title: /* Why empty? */
$nodelist = $xpath->query("//bk:name");
print "Last Book Title: ".$nodelist->item($nodelist->length - 1)->textContent."\n";
// Last Book Title: Grapes of Wrath /* Why not "Of Mice and Men" */
$nodelist = $xpath->query("//bk:name[ancestor::rare]");
print "Last Rare Book Title: ".$nodelist->item($nodelist->length - 1)->nodeValue."\n";
// Last Rare Book Title: /* Why empty? */
$xpath->registerNamespace("rt", "http://www.example.com/store");
$nodelist = $xpath->query("//bk:name[ancestor::rt:rare]");
print "Last Rare Book Title: ".$nodelist->item($nodelist->length - 1)->nodeValue."\n";
// Last Rare Book Title: Cannery Row
$xpath->registerNamespace("ext", "http://www.example.com/ExteralClassics");
$nodelist = $xpath->query("(//bk:name) | (//ext:name)");
print "Last Book Title: ".$nodelist->item($nodelist->length - 1)->textContent."\n";
// Last Book Title: To Kill a Mockingbird
DOM and Xpath w/Namespaces
dom/xpath/dom-xpathns.php
$xpath->registerNamespace("bk2", "http://www.example.com/classicbook");
$nodelist = $xpath->query("//bk2:name");
print "Last Book Title (bk2): "
print $nodelist->item($nodelist->length - 1)->textContent."\n";
// Last Book Title (bk2): Of Mice and Men
Complete Results:
Last Book Title:
Last Book Title: Grapes of Wrath
Last Rare Book Title:
Last Rare Book Title: Cannery Row
Last Book Title: To Kill a Mockingbird
Last Book Title (bk2): Of Mice and Men
Performing Validation
dom/validation/validate.php
$doc = new DOMDocument();
print "DTD Validation:\n";
$doc->load('courses-dtd.xml', LIBXML_DTDVALID);
/* No errors means document is valid */
if ($doc->validate()) { print " Document Is Valid\n";
}
print "DTD Validation FAILURE:\n";
$doc->load('course-id.xml');
if ($doc->validate()) { print " Document Is Valid\n";
}
$doc->load('course.xml');
print "\nXML Schema Validation:\n";
if ($doc->schemaValidate('course.xsd')) { print " Document is valid\n"; }
$doc->load('course.xml');
print "\nRelaxNG Validation:\n";
if ($doc->relaxNGValidate('course.rng')) {
print " Document is valid\n"; }
Performing Validation Results
DTD Validation:
Document Is Valid
DTD Validation FAILURE:
Warning: DOMDocument::validate(): No declaration for element courses in
/home/rrichards/workshop/dom/validation/validate.php on line 11
Warning: DOMDocument::validate(): No declaration for element course in
/home/rrichards/workshop/dom/validation/validate.php on line 11
Warning: DOMDocument::validate(): No declaration for element title in
/home/rrichards/workshop/dom/validation/validate.php on line 11
...
XML Schema Validation:
Document is valid
RelaxNG Validation:
Document is valid
Extending DOM Classes




Overriding the constructor requires the parent
constructor to be called.
Properties built into the DOM classes cannot be
overridden.
Methods built into the DOM classes may can be
overridden.
The lifespan of an extended object is that of the
object itself.
Extending DOM Classes
dom/extending/extending.php
class customElement extends DOMElement { }
class customDoc extends DOMDocument {
public $nodeName = "customDoc";
function __construct($rootName) {
parent::__construct();
if (! empty($rootName)) {
$element = $this->appendChild(new DOMElement($rootName)); }
}
function createElement($name, $value, $parent=NULL) {
$custom = new customElement($name, $value);
if ($parent && ($parent instanceof DOMElement)) {
$parent->appendChild($custom); }
return $custom;
}
}
$myc = new customDoc("root");
$myelement = $myc->createElement("myname", "myvalue", $myc->documentElement);
if ($myelement instanceof customElement) { print "This is a customElement\n"; }
print $myc->nodeName."\n";
print $myc->saveXML();
DOM Object Scope
dom/extending/object_scope.php
class customElement extends DOMElement { }
function changeit($doc) {
$myelement = new customElement("custom", "element2");
$doc->replaceChild($myelement, $doc->documentElement);
print "Within changeit function: ".get_class($doc->documentElement)."\n";
}
$doc = new DOMDocument();
$myelement = $doc->appendChild(new customElement("custom", "element"));
print "After Append: ".get_class($myelement)."\n";
unset($myelement);
print "After unset: ".get_class($doc->documentElement)."\n";
changeit($doc);
print "Outside changeit(): ".get_class($doc->documentElement)."\n";
After Append: customElement
After unset: DOMElement
Within changeit function: customElement
Outside changeit(): DOMElement
DOM: registerNodeClass
dom/extending/register_node_class.php
class customElement extends DOMElement { }
function changeit($doc) {
$myelement = new DOMElement("custom", "element2");
$doc->replaceChild($myelement, $doc->documentElement);
print "Within changeit function: ".get_class($doc->documentElement)."\n";
}
$doc = new DOMDocument();
$doc->registerNodeClass('DOMElement', 'customElement');
$myelement = $doc->appendChild($doc->createElement("custom", "element"));
print "After Append: ".get_class($myelement)."\n";
unset($myelement);
print "After unset: ".get_class($doc->documentElement)."\n";
changeit($doc);
print "Outside changeit(): ".get_class($doc->documentElement)."\n";
After Append: customElement
After unset: customElement
Within changeit function: DOMElement
Outside changeit(): customElement
DOM:Common Issues







DOM Objects and Sessions
Removing Nodes while iterating a Nodeset skips nodes
XML Tree contains garbled characters
Extended class is not returned from property or
method
Elements not being returned by ID
Entity errors are issues when loading a document
New DTD is not recognized by document
SimpleXML






Provides simple access to XML documents
Operates only on elements and attributes
Contains XPath support
Allows for modifications to the XML
Zero copy interoperability with DOM
New in PHP 5.1.3


Elements and attributes can be added using addChild() and
addAttribute() methods.
Node names can be retrieved by calling getName().
SimpleXML: Consuming Yahoo WebSearch
simplexml/yahoo_rest_results.xml
<ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="urn:yahoo:srch"
xsi:schemaLocation="urn:yahoo:srch
http://api.search.yahoo.com/WebSearchService/V1/WebSearchResponse.xsd"
totalResultsAvailable="374000" totalResultsReturned="5" firstResultPosition="1">
<Result>
<Title>Zend Technologies - PHP 5 In Depth - XML in PHP 5 - What's
New?</Title>
<Summary>XML in PHP 5 - What's New? By Christian Stocker. March . . .
</Summary>
<Url>http://www.zend.com/php5/articles/php5-xmlphp.php</Url>
<ClickUrl>http://uk.wrs.yahoo.com/_ylt=. . .</ClickUrl>
<ModificationDate>1143014400</ModificationDate>
<MimeType>text/html</MimeType>
<Cache>
<Url>http://uk.wrs.yahoo.com/...</Url>
<Size>112625</Size>
</Cache>
</Result>
...
</Results>
SimpleXML: Consuming Yahoo WebSearch
simplexml/reading_rest.php
/* URL to Web Search service */
$url = 'http://api.search.yahoo.com/WebSearchService/V1/webSearch';
/* The query is separate here as the terms must be encoded. */
$url .= '?query='.rawurlencode('php5 xml');
/* Complete the URL adding App ID, limit to 5 results and only English results */
$url .= "&appid=zzz&results=5&language=en";
$sxe = simplexml_load_file($url);
/* Check for number of results returned */
if ((int)$sxe['totalResultsReturned'] > 0) {
/* Loop through each result and output title, url and modification date */
foreach ($sxe->Result AS $result) {
print 'Title: '.$result->Title."\n";
print 'Url: '.$result->Url."\n";
}
}
print 'Mod Date: '.date ('M d Y', (int)$result->ModificationDate)."\n\n";
SimpleXML: Consuming Yahoo WebSearch
RESULTS
Title: Zend Technologies - PHP 5 In Depth - XML in PHP 5 - What's New?
Url: http://www.zend.com/php5/articles/php5-xmlphp.php
Mod Date: Sep 06 2006
Title: FreshPorts -- textproc/php5-xml
Url: http://www.freshports.org/textproc/php5-xml
Mod Date: Aug 31 2006
Title: PHP5 XML support? - Dev Shed
Url: http://forums.devshed.com/php-development-5/php5-xml-support-69898.html
Mod Date: Aug 21 2006
Title: ONLamp.com -- Using PHP 5's SimpleXML
Url: http://www.onlamp.com/pub/a/php/2004/01/15/simplexml.html
Mod Date: Sep 06 2006
Title: PHP5 XML/XSL - Rendering strings? - PHP
Url: http://www.thescripts.com/forum/thread5141.html
Mod Date: Sep 01 2006
<store xmlns="http://www.example.com/store" xmlns:bk="http://www.example.com/book">
<books>
<rare>
<bk:book qty="4">
<bk:name>Cannery Row</bk:name>
<bk:price>400.00</bk:price><bk:edition>1</bk:edition>
</bk:book>
</rare>
<classics>
<bk:book qty="25">
<bk:name>Grapes of Wrath</bk:name>
<bk:price>12.99</bk:price>
</bk:book>
<bk:book qty="25" xmlns:bk="http://www.example.com/classicbook">
<bk:name>Of Mice and Men</bk:name>
<bk:price>9.99</bk:price>
</bk:book>
</classics>
<classics xmlns="http://www.example.com/ExteralClassics">
<book qty="33">
<name>To Kill a Mockingbird</name>
<price>10.99</price>
</book>
</classics>
</books>
</store>
SimpleXML: Namespaces
simplexml/simplexml-namespace.php
$store = simplexml_load_file('simplexml-xpathns.xml');
$books = $store->books;
foreach ($books->classics AS $classic) {
if ($classic->book)
print $classic->book->name."\n\n";
}
/* Why only one result?: To Kill a Mockingbird */
$x = 0;
foreach ($books->classics AS $classics) {
if ($x++ == 0) {
$children = $classics->children("http://www.example.com/classicbook");
/* Print name for the books where book element resides in a prefixed namespace */
print $classics->children("http://www.example.com/book")->book->name."\n";
print $children->book->name."\n";
} else
print $classic->book->name."\n";
}
SimpleXML: Namespaces
Results
To Kill a Mockingbird
Grapes of Wrath
Of Mice and Men
To Kill a Mockingbird
SimpleXML: Xpath
simplexml/simplexml-xpathns.php
$sxe = simplexml_load_file('simplexml-xpathns.xml');
$nodelist = $sxe->xpath("//bk:name");
print "Last Book Title: ".$nodelist[count($nodelist) - 1]."\n";
$sxe->registerXPathNamespace("rt", "http://www.example.com/store");
$nodelist = $sxe->xpath("//bk:name[ancestor::rt:rare]");
print "Last Rare Book Title: ".$nodelist[count($nodelist) - 1]."\n";
$sxe->registerXPathNamespace("ext", "http://www.example.com/ExteralClassics");
$nodelist = $sxe->xpath("(//bk:name) | (//ext:name)");
print "Last Book Title: ".$nodelist[count($nodelist) - 1]."\n";
$sxe->registerXPathNamespace("bk2", "http://www.example.com/classicbook");
$nodelist = $sxe->xpath("//bk2:name");
print "Last Book Title (bk2): ".$nodelist[count($nodelist) - 1]."\n";
SimpleXML: XPath Results
Last Book Title: Grapes of Wrath
Last Rare Book Title: Cannery Row
Last Book Title: To Kill a Mockingbird
Last Book Title (bk2): Of Mice and Men
SimpleXML: Advanced Editing
simplexml/editing.php
$data = array(array('title'=>'Result 1', 'descript'=>'Res1 description'),
array('title'=>'Result 2', 'descript'=>'description of Res2'),
array('title'=>'Result 3', 'descript'=>'This is result 3'));
class webservice extends simpleXMLElement {
public function appendElement($name, $value=NULL) {
$node = dom_import_simplexml($this);
$newnode = $value ? new DOMElement($name, $value) : new DOMElement($name);
$node->appendChild($newnode);
return simplexml_import_dom($newnode, 'webservice');
}}
$rest = simplexml_load_string('<results num="0" />', 'webservice');
$rest['num'] = count($data);
foreach ($data AS $result_item) {
$result = $rest->appendElement('result');
$result->appendElement('title', $result_item['title']);
$result->appendElement('description');
$result->description = $result_item['descript'];
}
print $rest->asXML();
SimpleXML: Advanced Editing
Results
<?xml version="1.0"?>
<results num="3">
<result>
<title>Result 1</title>
<description>Res1 description</description>
</result>
<result>
<title>Result 2</title>
<description>description of Res2</description>
</result>
<result>
<title>Result 3</title>
<description>This is result 3</description>
</result>
</results>
SimpleXML: Advanced Editing
PHP 5.1.3
simplexml/editing_php513.php
$data = array(array('title'=>'Result 1', 'descript'=>'Res1 description'),
array('title'=>'Result 2', 'descript'=>'description of Res2'),
array('title'=>'Result 3', 'descript'=>'This is result 3'));
$rest = simplexml_load_string('<results num="0" />');
$rest['num'] = count($data);
foreach ($data AS $result_item) {
$result = $rest->addChild('result');
$result->addChild('title', $result_item['title']);
$result->addChild('description');
$result->description = $result_item['descript'];
}
$rest->asXML('editing_php513.xml');
SimpleXML: Removing data
remove_data.php
<?php
$results = simplexml_load_file('editing_php513.xml');
/* Delete title from first result element */
unset($results->result->title);
/* Delete the 2nd result element - ONLY WORKS in PHP 5.1.3 */
unset($results->result[1]);
print $results->asXML();
?>
SimpleXML: Removing data
RESULTS
<?xml version="1.0"?>
<results num="3">
<result>
<description>Res1 description</description>
</result>
<result>
<title>Result 3</title>
<description>This is result 3</description>
</result>
</results>
Simple API for XML (SAX)





Event based push parser
Low memory usage
Works using function callbacks
Almost completely compatible with ext/xml
from PHP 4
Default encoding is UTF-8 rather than
ISO-8859-1 as it was in PHP 4
SAX: Source Document
xml/xml_simple.xml
<?xml version='1.0'?>
<chapter xmlns:a="http://www.example.com/namespace-a"
xmlns="http://www.example.com/default">
<a:title>ext/xml</a:title>
<para>
First Paragraph
</para>
<a:section a:id="about">
<title>About this Document</title>
<para>
<!-- this is a comment -->
<?php echo 'Hi! This is PHP version ' . phpversion(); ?>
</para>
</a:section>
</chapter>
SAX: Simple Example
xml/xml_simple.php
<?php
function startElement($parser, $elementname, $attributes) {
print "* Start Element: $elementname \n";
foreach ($attributes as $attname => $attvalue) {
print "
$attname => $attvalue \n";
}
}
function endElement($parser, $elementname) {
print "* End Element: $elementname\n";
}
function charDataHandler($parser,$data) {
if (trim($data) != "") print $data."\n";
}
function PIhandler ($parser, $target, $data) {
print "PI: $target -> $data\n";
}
function DefaultHandler($parser, $data) {
print "Default: $data\n";
}
SAX: Simple Example
xml/xml_simple.php
$parser = xml_parser_create();
/* Disable as case is significant in XML */
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING,
false);
xml_set_element_handler($parser,"startElement","endElement");
xml_set_character_data_handler($parser, "charDataHandler");
xml_set_processing_instruction_handler ($parser,
"PIhandler");
xml_set_default_handler ($parser, "DefaultHandler");
if (($fp = fopen("xml_simple.xml", "r"))) {
while ($data = fread($fp, 4096)) {
xml_parse($parser, $data, feof($fp));
}
}
?>
SAX: Simple Example
RESULTS
* Start Element: chapter
xmlns:a => http://www.example.com/namespace-a
xmlns => http://www.example.com/default
* Start Element: a:title
ext/xml
* End Element: a:title
* Start Element: para
First Paragraph
* End Element: para
* Start Element: a:section
a:id => about
* Start Element: title
About this Document
* End Element: title
* Start Element: para
Default: <!-- this is a comment -->
PI: php -> echo 'Hi! This is PHP version ' . phpversion();
* End Element: para
* End Element: a:section
* End Element: chapter
SAX: Error Handling
xml/xml_error.php
<?php
/* Malformed document */
$data = "<root>";
$parser = xml_parser_create();
if(! xml_parse($parser, $data, TRUE)) {
/* Normally die is or some other escape mechanism is also
called*/
printf("XML error: %s in line %d, column %d\n\n",
xml_error_string(xml_get_error_code($parser)),
xml_get_current_line_number($parser),
xml_get_current_column_number($parser));
}
/* Magically you can also get a structured error */
$xmlError = libxml_get_last_error();
var_dump($xmlError);
?>
SAX: Error Handling
RESULTS
XML error: Invalid document end in line 1, column 7
object(LibXMLError)#1 (6) {
["level"]=>
int(3)
["code"]=>
int(5)
["column"]=>
int(7)
["message"]=>
string(41) "Extra content at the end of the document
"
["file"]=>
string(0) ""
["line"]=>
int(1)
}
SAX: Advanced Example
xml/xml_advanced.php
class cSax {
function startElement($parser, $elementname, $attributes) {
list($namespaceURI,$localName)= split("@",$elementname);
if (! $localName) {
$localName = $namespaceURI;
$namespaceURI = "";
}
print "* Start Element: $localName".
($namespaceURI ? " in $namespaceURI" : "")."\n";
foreach ($attributes as $attname => $attvalue) {
print "
$attname => $attvalue \n";
}
}
function endElement($parser, $elementname) {
list($namespaceURI,$localName)= split("@",$elementname);
if (! $localName) {
$localName = $namespaceURI;
$namespaceURI = "";
}
print "* End Element: $localName".
($namespaceURI ? " in $namespaceURI" : "")."\n";
}
}
SAX: Advanced Example
xml/xml_advanced.php
$objcSax = new cSax();
$parser = xml_parser_create_ns("ISO-8859-1","@");
/* Disable as case is significant in XML */
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING,
false);
xml_set_object($parser, $objcSax);
xml_set_element_handler($parser,"startElement","endElement");
if (($fp = fopen("xml_simple.xml", "r"))) {
while ($data = fread($fp, 4096)) {
if (! xml_parse($parser, $data, feof($fp))) {
$xmlError = libxml_get_last_error();
var_dump($xmlError);
exit;
}
}
}
SAX: Advanced Example
RESULTS
*
*
*
*
*
*
*
*
*
*
*
*
Start Element: chapter in http://www.example.com/default
Start Element: title in http://www.example.com/namespace-a
End Element: title in http://www.example.com/namespace-a
Start Element: para in http://www.example.com/default
End Element: para in http://www.example.com/default
Start Element: section in
http://www.example.com/namespace-a
http://www.example.com/namespace-a@id => about
Start Element: title in http://www.example.com/default
End Element: title in http://www.example.com/default
Start Element: para in http://www.example.com/default
End Element: para in http://www.example.com/default
End Element: section in http://www.example.com/namespace-a
End Element: chapter in http://www.example.com/default
XMLReader




Forward moving stream based parser
It is a Pull parser
Based on the C# XmlTextReader API
Advantages:






Low memory footprint
Namespace support
Simple API
Validation support
Advanced Feature Set
Faster Processing
XMLReader: Simple Example
xmlreader/reader_simple.xml
<?xml version='1.0'?>
<chapter xmlns:a="http://www.example.com/namespace-a"
xmlns="http://www.example.com/default">
<a:title>XMLReader</a:title>
<para>
First Paragraph
</para>
<a:section a:id="about">
<title>About this Document</title>
<para>
<!-- this is a comment -->
<?php echo 'Hi! This is PHP version ' . phpversion(); ?>
</para>
</a:section>
</chapter>
XMLReader: Simple Example
xmlreader/reader_simple.php
$reader = new XMLReader();
$reader->open('reader_simple.xml');
$reader->read();
print "xmlns Attribute value: ".$reader->getAttributeNo(0)."\n\n";
while ($reader->read() && $reader->name != "a:title") { }
print "Local Name for Element: ".$reader->localName."\n";
print "Namespace URI for Element: ".$reader->namespaceURI."\n";
while($reader->read()) {
switch ($reader->nodeType) {
case XMLReader::ELEMENT:
print "Element: ".$reader->name."\n";
if ($reader->hasAttributes && $reader->moveToFirstAttribute()) {
do {
print " ".$reader->name."=".$reader->value."\n";
} while($reader->moveToNextAttribute());
}
break;
case XMLReader::PI:
}
}
print "PI Target: ".$reader->name."\n PI Data: ".$reader->value."\n";
XMLReader: Simple Example
RESULTS
Local Name for Element: title
Namespace URI for Element: http://www.example.com/namespace-a
Element: para
Element: a:section
a:id=about
Element: title
Element: para
PI Target: php
PI Data: echo 'Hi! This is PHP version ' . phpversion();
XMLReader: Consuming Yahoo
Shopping
<?xml version="1.0" encoding="ISO-8859-1"?>
<ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:yahoo:prods"
xsi:schemaLocation="urn:yahoo:prods
http://api.shopping.yahoo.com/shoppingservice/v1/productsearch.xsd"
totalResultsAvailable="8850" firstResultPosition="2" totalResultsReturned="2">
<Result>
<Catalog ID="1991433722">
<Url><![CDATA[http://shopping.yahoo.com/p:Linksys. . .2]]></Url>
<ProductName><![CDATA[Linksys WRT5. . .r Broadband
Router]]></ProductName>
<PriceFrom>59.99</PriceFrom>
<PriceTo>100.00</PriceTo>
<Thumbnail /><!-- child elements Url (CDATA), Height, Width -->
<Description><![CDATA[The Wireless-G . . .ces.]]></Description>
<Summary><![CDATA[IEEE 802.3, ...]]></Summary>
<UserRating /><!-- Rating sub elements -->
<SpecificationList /><!-- 0+ Specification child elements -->
</SpecificationList>
</Catalog>
</Result>
</ResultSet>
XMLReader: Consuming Yahoo Shopping
xmlreader/rest_yahoo_shopping.php
function getTextValue($reader) { ... }
function processCatalog($reader) { ... }
function processResult($reader) { ... }
/* URL to Product Search service */
$url = 'http://api.shopping.yahoo.com/ShoppingService/V1/productSearch';
/* The query is separate here as the terms must be encoded. */
$url .= '?query='.rawurlencode('linksys');
/* Complete the URL with App ID, limit to 1 result and start at second record */
$url .= "&appid=zzz&results=2&start=2";
$reader = new XMLReader();
if (! $reader->open($url)) { print "Cannot access Webservice\n"; exit; }
while($reader->name != "Result") { $reader->read(); }
do {
processResult($reader);
} while($reader->next('Result'));
XMLReader: Consuming Yahoo Shopping
xmlreader/rest_yahoo_shopping.php
function getTextValue($reader) {
if ($reader->nodeType != XMLReader::ELEMENT || $reader->isEmptyElement
|| ($reader->read() && $reader->nodeType == XMLReader::END_ELEMENT))
return;
$retVal = $reader->value;
$reader->read();
return $retVal;
}
function processResult($reader) {
$depth = $reader->depth;
if ($reader->isEmptyElement || ($reader->read() &&
$reader->nodeType == XMLReader::END_ELEMENT))
return;
}
while($depth < $reader->depth && $reader->name != "Catalog") { $reader->read(); };
processCatalog($reader);
/* Read until </Result> is encountered */
while($depth < $reader->depth) { $reader->read(); }
XMLReader: Consuming Yahoo Shopping
xmlreader/rest_yahoo_shopping.php
function processCatalog($reader) {
$depth = $reader->depth;
print "Catalog ID".$reader->getAttribute('ID')."\n";
if ($reader->isEmptyElement || ($reader->read() &&
$reader->nodeType == XMLReader::END_ELEMENT))
return;
while($depth < $reader->depth) {
switch ($reader->name) {
case "ProductName":
case "PriceFrom":
case "PriceTo":
case "Description":
case "Url":
print $reader->name.": ".getTextValue($reader)."\n";
}
$reader->next();
}
}
XMLReader: Consuming Yahoo Shopping
RESULTS (Abbreviated)
Catalog ID1990338714
Url:
http://shopping.yahoo.com/p:Linksys%20Instant%20Broadband%20EtherF
ast%20Cable%2FDSL%20Router:1990338714
ProductName: Linksys Instant Broadband EtherFast Cable/DSL Router
PriceFrom: 39.99
PriceTo: 72.71
Description: <P>Linksys, a provider of networking hardware for the
small/medium business (SMB), small office/home office (SOHO), and
enterprise markets and broadband networking hardware for the home, has
announced the new EtherFast Cable/DSL Router. The first in the new
Instant Broadband series, this Linksys broadband router will enable home or
office users to connect their computers to a cable or DSL modem and
securely share Internet access and perform networking tasks such as file and
printer sharing. The built-in hardware firewall gives users the security of
sharing files without fear of intruders hacking into the network. </P>
XMLReader: DTD Validation
xmlreader/validation/reader.xml
<!DOCTYPE chapter [
<!ELEMENT chapter (title, para, section)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT para ANY>
<!ATTLIST para name CDATA "default">
<!ELEMENT section (#PCDATA)>
<!ATTLIST section id ID #REQUIRED>
]>
<chapter>
<title>XMLReader</title>
<para>
First Paragraph
</para>
<section id="about">
<title>About this Document</title>
<para>content</para>
</section>
</chapter>
XMLReader: DTD Validation
xmlreader/validation/reader.php
$objReader = XMLReader::open('reader.xml');
$objReader->setParserProperty(XMLReader::VALIDATE, TRUE);
/* As of PHP 5.2 LIBXML Parser Options may be passed */
// $objReader = XMLReader::open('reader.xml', NULL, LIBXML_DTDVALID);
libxml_use_internal_errors(TRUE);
while ($objReader->read()) {
if (! $objReader->isValid()) {
print "NOT VALID\n";
break;
}
}
$arErrors = libxml_get_errors();
foreach ($arErrors AS $xmlError) {
print $xmlError->message;
}
XMLReader: DTD Validation
RESULTS
NOT VALID
Element section was declared #PCDATA but contains non
text nodes
XMLReader: Relax NG Validation
xmlreader/validation/reader.rng
<?xml version="1.0" encoding="utf-8" ?>
<element name="chapter"
xmlns="http://relaxng.org/ns/structure/1.0">
<element name="title">
<text/>
</element>
<element name="para">
<text/>
</element>
<element name="section">
<attribute name="id" />
<text/>
</element>
</element>
XMLReader: Relax NG Validation
xmlreader/validation/reader-rng.php
$objReader = XMLReader::open('reader.xml');
$objReader->setRelaxNGSchema('reader.rng');
libxml_use_internal_errors(TRUE);
while ($objReader->read()) {
if (! $objReader->isValid()) {
print "NOT VALID\n";
break;
}
}
$arErrors = libxml_get_errors();
foreach ($arErrors AS $xmlError) {
print $xmlError->message;
}
XMLReader: Relax NG Validation
RESULTS
NOT VALID
Did not expect element title there
XSL







Used to transform XML data
XSLT based on XPath
Works with DOM and SimpleXML, although the
DOM extension is required.
Provides the capability of calling PHP functions during
a transformation
DOM nodes may be returned from PHP functions
The LIBXML_NOCDATA and LIBXML_NOENT
constants are your friends.
libxslt 1.1.5+ is recommended to avoid problems when
using xsl:key
XSL: XML Input Data
xsl/sites.xml
<?xml version="1.0"?>
<sites>
<site xml:id="php-gen">
<name>PHP General</name>
<url>http://news.php.net/group.php?group=php.general&amp;format=rss</url>
</site>
<site xml:id="php-pear">
<name>PHP Pear Dev</name>
<url>http://news.php.net/group.php?group=php.pear.dev&amp;format=rss</url>
</site>
<site xml:id="php-planet">
<name>Planet PHP</name>
<url>http://www.planet-php.org/rss/</url>
</site>
</sites>
XSL: Simple Transformation
xsl/simple_stylesheet.xsl
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="/sites/site"/>
</body>
</html>
</xsl:template>
<xsl:template match="/sites/site">
<p><xsl:value-of select="./name"/> : <xsl:value-of select="./url"
disable-output-escaping="yes"/></p>
</xsl:template>
</xsl:stylesheet>
XSL: Simple Transformation
xsl/simple_stylesheet.php
/* Load Stylesheet */
$stylesheet = new DOMDocument();
$stylesheet->load('simple_stylesheet.xsl');
/* Create XSL Processor */
$proc = new xsltprocessor();
$proc->importStylesheet($stylesheet);
/* Load XML Data */
$dom = new DOMDocument();
$dom->load('sites.xml');
print $proc->transformToXML($dom);
XSL: Simple Transformation
RESULTS
<html>
<body>
<p>PHP General :
http://news.php.net/group.php?group=php.general&format=rss</p>
<p>PHP Pear Dev :
http://news.php.net/group.php?group=php.pear.dev&format=rss</p>
<p>Planet PHP : http://www.planet-php.org/rss/</p>
</body>
</html>
XSL: Advanced Transformation
xsl/advanced_stylesheet.php
function initReader($url) {
$GLOBALS['reader'] = new XMLReader();
if ($GLOBALS['reader']->open($url)) {
while ($GLOBALS['reader']->read() && $GLOBALS['reader']->name != 'item') { }
if ($GLOBALS['reader']->name == 'item')
return 1;
}
$GLOBALS['reader'] = NULL;
return 0;
}
function readNextItem() {
if ($GLOBALS['reader'] == NULL)
return NULL;
if ($GLOBALS['beingProc'])
$GLOBALS['reader']->next('item');
else
$GLOBALS['beingProc'] = TRUE;
if ($GLOBALS['reader']->name == 'item')
return $GLOBALS['reader']->expand();
return NULL;
}
XSL: Advanced Transformation
xsl/advanced_stylesheet.php
$beingProc = FALSE;
$reader = NULL;
/* Load Stylesheet */
$stylesheet = new DOMDocument();
$stylesheet->load('advanced_stylesheet.xsl');
/* Create XSL Processor */
$proc = new xsltprocessor();
$proc->importStylesheet($stylesheet);
/* Load XML Data */
$dom = new DOMDocument();
$dom->load('sites.xml');
$proc->setParameter(NULL, 'siteid', 'php-gen');
$proc->registerPHPFunctions('initReader');
$proc->registerPHPFunctions('readNextItem');
print $proc->transformToXML($dom);
/* END */
XSL: Advanced Transformation
xsl/advanced_stylesheet.xsl
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl" version="1.0">
<xsl:output method="html"/>
<xsl:param name="siteid" select="0" />
<xsl:template match="/">
<html><body>
<xsl:apply-templates select="id($siteid)"/>
</body></html>
</xsl:template>
<xsl:template match="/sites/site">
<xsl:variable name="itemnum" select="php:functionString('initReader', ./url)" />
<xsl:if test="number($itemnum) > 0">
<xsl:call-template name="itemproc" />
</xsl:if>
</xsl:template>
XSL: Advanced Transformation
xsl/advanced_stylesheet.xsl
<xsl:template match="item">
<p>
Title: <b><xsl:value-of select="./title" /></b><br/><br/>
URL: <xsl:value-of select="./link" /><br/>
Published: <xsl:value-of select="./pubDate" /><br/>
</p>
</xsl:template>
<xsl:template name="itemproc">
<xsl:variable name="nodeset" select="php:functionString('readNextItem')" />
<xsl:if test="boolean($nodeset)">
<xsl:apply-templates select="$nodeset"/>
<xsl:call-template name="itemproc" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
XSL: Advanced Transformation
Results viewed through a browser
xsl/advanced_stylesheet.html
Title: Re: Spreadsheet Writer
URL: http://news.php.net/php.general/241446
Published: Thu, 07 Sep 2006 13:52:09 –0400
Title: Re: Spreadsheet Writer
URL: http://news.php.net/php.general/241447
Published: Thu, 07 Sep 2006 13:52:09 -0400
Title: Re: Spreadsheet Writer
URL: http://news.php.net/php.general/241448
Published: Thu, 07 Sep 2006 13:52:09 -0400
XMLWriter





Lightweight and forward-only API for
generating well formed XML
Automatically escapes data
Works with PHP 4.3+ available at
http://pecl.php.net/package/xmlwriter
Object Oriented API available for PHP 5+
Part of core PHP distribution since PHP 5.1.2
XMLWriter: Simple Example
xmlwriter/simple.php
<?php
$xw = new XMLWriter();
$xw->openMemory();
/* Turn on indenting to make output look pretty and set string
used for indenting as teh default space is too short*/
$xw->setIndent(TRUE);
$xw->setIndentString(' ');
/* Write out the optional XML declaration only specifying version */
$xw->startDocument('1.0');
/* Create the opening document element, which is namespaced */
$xw->startElementNs(NULL, "chapter", "http://www.example.com/default");
/* Write out an xml namespace declaration that is used later in the document */
$res = $xw->writeAttribute('xmlns:a', 'http://www.example.com/namespace-a');
/* Write complete elements with text content */
$xw->writeElement('a:title', 'XMLReader');
$xw->writeElement('para', 'spec chars < > & " inside para element');
XMLWriter: Simple Example
xmlwriter/simple.php
/* start an element and add an attribute to it */
$xw->startElement('a:section');
$xw->writeAttribute('a:id', 'about');
/* Write out an element with special characters */
$xw->writeElement('title', 'Pro PHP XML & Webservices');
$xw->startElement('para'); /* This opens the para element */
$xw->writeComment("this is a comment");
$xw->text("
");
$xw->writePi("php", "echo 'Hi! This is PHP version ' . phpversion(); ");
$xw->text("\n ");
$xw->endElement(); /* This will close the open para element */
$xw->endDocument();
/* Flush and clear the buffer */
echo $xw->flush(true);
?>
XMLWriter: Simple Example
xmlwriter/simple.php
/* start an element and add an attribute to it */
$xw->startElement('a:section');
$xw->writeAttribute('a:id', 'about');
/* Write out an element with special characters */
$xw->writeElement('title', 'Pro PHP XML & Webservices');
$xw->startElement('para'); /* This opens the para element */
$xw->writeComment("this is a comment");
$xw->text("
");
$xw->writePi("php", "echo 'Hi! This is PHP version ' . phpversion(); ");
$xw->text("\n ");
$xw->endElement(); /* This will close the open para element */
$xw->endDocument();
/* Flush and clear the buffer */
echo $xw->flush(true);
?>
XMLWriter: Creating a Rest Service
xmlwriter/rest.php (startid and maxid)
<?php
/* If the database does not exist, then create it and populate it with some data */
if (! file_exists('xmlwriterdb')) {
if ($dbhandle = sqlite_open('xmlwriterdb', 0666)) {
sqlite_query($dbhandle, 'CREATE TABLE xmlwriter (id int, name varchar(15))');
for ($x=1; $x< 11; $x++) {
sqlite_query($dbhandle,
"INSERT INTO xmlwriter VALUES (".$x.", 'Data Num: ".$x."')");
}
sqlite_close($dbhandle);
} } /* closes function and saves display space */
/* Retrieve record based on id(s) */
function getDBData($min, $max) {
$results = NULL;
if ($dbhandle = sqlite_open('xmlwriterdb')) {
$strSQL = 'SELECT id, name FROM xmlwriter where id>='.$min.' and id
<='.$max;
$query = sqlite_query($dbhandle,$strSQL);
return sqlite_fetch_all($query, SQLITE_ASSOC);
} } /* closes function and saves display space */
XMLWriter: Creating a Rest Service
xmlwriter/rest.php
/* Setup defaults */
$recid = 0;
$minid = 0;
$maxid = 0;
/* Retrieve requested record id(s) and insure $maxid is never less than $minid */
if (! empty($_GET['startid'])) {
$minid = (int) $_GET['startid'];
$maxid = $minid;
if (! empty($_GET['maxid'])) {
$maxid = (int) $_GET['maxid'];
if ($minid > $maxid) $maxid = $minid;
}
}
/* Retrieve the requested records from the database */
$arResults = getDBData($minid, $maxid);
XMLWriter: Creating a Rest Service
xmlwriter/rest.php
/* Process the resulting records if any */
header('Content-Type: text/xml');
$xw = new XMLWriter();
/* Send the XML document directly to output as it is written */
$xw->openUri('php://output');
$xw->startDocument('1.0', 'UTF-8');
$xw->startElement('Results');
foreach ($arResults AS $result) {
$xw->startElement('Result');
foreach ($result AS $field_name => $field_value) {
$xw->writeElement($field_name, $field_value);
}
$xw->endElement();
/* Progressively send the output */
$xw->flush();
}
$xw->endDocument();
/* Flush and clear the buffer */
$xw->flush();
XMLWriter: Creating a Rest Service
xmlwriter/rest.php
/* Process the resulting records if any */
header('Content-Type: text/xml');
$xw = new XMLWriter();
/* Send the XML document directly to output as it is written */
$xw->openUri('php://output');
$xw->startDocument('1.0', 'UTF-8');
$xw->startElement('Results');
foreach ($arResults AS $result) {
$xw->startElement('Result');
foreach ($result AS $field_name => $field_value) {
$xw->writeElement($field_name, $field_value);
}
$xw->endElement();
/* Progressively send the output */
$xw->flush();
}
$xw->endDocument();
/* Flush and clear the buffer */
$xw->flush();
Tree Parsers

Pros:
Full navigation and modification of the XML
document
 Navigating and searching are extremely fast once the
tree is loaded into memory


Cons:
Must wait until entire tree is loaded to begin working
with the XML.
 Memory intensive

Streaming Parsers

Pros:
Uses minimal memory
 Processing takes place immediately while the
document is parsed


Cons:
Minimal to no navigation support (forward only)
 No document editing capabilities

Raw Test Data
<books>
<book id="1"><title>1</title><pages>1</pages></book>
<book id="2"><title>2</title><pages>2</pages></book>
<!-- Remaining book elements for a total of 200,000 -->
</books>
Memory Usage:
DOM
SimpleXML
ext/xml
XMLReader
85.6MB
85.6MB
26KB
177KB
Using every optimization possible the following results show the time in
seconds to locate the book element having id="5000".
Average Time in Seconds for Optimized Search for an Element:
DOM
SimpleXML
ext/xml
XMLReader
6.623
6.583
0.930
0.238
SOAP





An XML-based protocol for exchanging information
between applications
It allows for remote invocation of methods in a
distributed environment
Uses existing transport protocols such as HTTP
Can operate with or without a Web Service Definition
Language (WSDL) document
A W3C standard and the core component to the Web
Services Interoperability Organization (WS-I) Basic
Profile
SOAP: Basic WSDL Structure
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/">
<types><!-- definition of types used in WSDL --></types>
<message><!-- abstract definition of the data being transmitted --></message>
<portType>
<!-- a set of abstract operations refrring to input and output messages -->
</portType>
<binding><!-- concrete protocol and data format specs --></binding>
<service><!-- specifies locations and bindings for a service --></service>
</definitions>
SOAP: Basic Message Structure
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<!-- Information to extend message -->
<!-- For example WS-Security or transaction information -->
</soap:Header>
<soap:Body>
<!-- Either the message contents or soap:Fault -->
<soap:Fault>
<!-- SOAP Fault structure and data -->
</soap:Fault>
</soap:Body>
</soap:Envelope>
SOAP: The SoapClient
SoapClient::__construct ( mixed wsdl [, array options] )
Some SoapClient options:
location*
(string) Location of Soap service
uri*
(string) Target namespace for the SOAP server
style1
(int) Binding style for message
(SOAP_DOCUMENT or SOAP_RPC)
use1
(int) Binding type for style
(SOAP_ENCODED or SOAP_LITERAL)
trace
(bool) Enable / disable request/response tracing
(default disabled)
exceptions
(bool) Turn Soap exceptions on / off (default on)
*Required in NON-WSDL mode
1 Only used in NON-WSDL mode
SOAP: The SoapClient
SoapClient::__construct ( mixed wsdl [, array options] )
Connection and security options for SoapClient:
login
Login for HTTP authentication
password
Password for HTTP authenication
proxy_host
Host for Proxy server
proxy_port
Port for Proxy server
proxy_login
Login for Proxy server
proxy_password
Password for Proxy server
local_cert
Client certificate for HTTPS client authentication
passphrase
Passphrase for client certificate
SOAP: Function Query a WSDL
soap/google/google_get_functions.php
<?php
try{
/* Create the SoapClient and load the WSDL */
$GoogleClient = new SoapClient('GoogleSearch.wsdl');
/* Retrieve all defined functions into an array */
$google_funcs = $GoogleClient->__getFunctions();
foreach($google_funcs AS $function) {
echo $function."\n\n";
}
} catch (SoapFault $e) {
var_dump($e);
}
?>
SOAP: Function Query a WSDL
Google Function RESULTS
base64Binary doGetCachedPage(string $key, string $url)
string doSpellingSuggestion(string $key, string $phrase)
GoogleSearchResult doGoogleSearch(string $key, string $q, int
$start, int $maxResults, boolean $filter, string $restrict, boolean
$safeSearch, string $lr, string $ie, string $oe)
$key refers to a Google license key, which may be obatined from:
http://www.google.com/apis/index.html
SOAP: Type Query a WSDL
soap/google/google_get_types.php
<?php
try{
/* Create the SoapClient and load the WSDL */
$GoogleClient = new SoapClient('GoogleSearch.wsdl');
/* Retrieve all defined types into an array */
$types = $GoogleClient->__getTypes();
foreach($ types AS $type) {
echo $type."\n\n";
}
} catch (SoapFault $e) {
var_dump($e);
}
?>
SOAP: Type Query a WSDL
Google Type RESULTS
struct GoogleSearchResult {
boolean documentFiltering;
string searchComments;
int estimatedTotalResultsCount;
boolean estimateIsExact;
ResultElementArray resultElements;
string searchQuery;
int startIndex;
int endIndex;
string searchTips;
DirectoryCategoryArray directoryCategories;
double searchTime;
}
ResultElement ResultElementArray[ ]
DirectoryCategory DirectoryCategoryArray[ ]
struct ResultElement {
string summary;
string URL;
string snippet;
string title;
string cachedSize;
boolean relatedInformationPresent;
string hostName;
DirectoryCategory
directoryCategory;
string directoryTitle;
}
struct DirectoryCategory {
string fullViewableName;
string specialEncoding;
}
SOAP: Retrieving from Google Cache
soap/google/google_cache_client.php
<?php
/* The following file holds your registered Google key */
require('google_key.php');
try {
/* Create the Soap Client */
$client = new SoapClient('http://api.google.com/GoogleSearch.wsdl');
$cached = $client->doGetCachedPage($key, 'http://www.google.com/');
/* display first 200 characters of cached page */
echo substr($cached, 0, 500);
} catch (SoapFault $e) {
var_dump($e);
}
?>
SOAP: Retrieving from Google Cache
RESULTS
<meta http-equiv="Content-Type" content="text/html;
charset=US-ASCII">
<BASE HREF="http://www.google.com/"><table border=1
width=100%><tr><td><table border=1 bgcolor=#ffffff
cellpadding=10 cellspacing=0 width=100%
color=#ffffff><tr><td><font face="" color=black size=1>This is <b><font color=#0039b6>G</font> <font
color=#c41200>o</font> <font color=#f3c518>o</font>
<font color=#0039b6>g</font> <font
color=#30a72f>l</font> <font
color=#c41200>e</font></b>'s <a
href="http://www.google.com/help/fea
SOAP: Google Search Client
soap/google/google_search_client.php
<?php
/* The following file holds your registered Google key */
require('google_key.php');
/* Define search criteria */
$search_terms = "PHP XML Web Services book";
$start = 0;
$maxResults = 10;
$filter = FALSE;
$safeSearch = TRUE;
$restrict = $lr = $ie = $oe = "";
/* Within Try/Catch block – omitted to save space */
/* Create the Soap Client */
$client = new SoapClient('http://api.google.com/GoogleSearch.wsdl');
?>
$results = $client->doGoogleSearch($key, $search_terms, $start, $maxResults,
$filter, $restrict, $safeSearch, $lr, $ie, $oe));
var_dump($results);
SOAP: Google Search Client
RESULTS
object(stdClass)#2 (11) {
["documentFiltering"] => bool(false)
["searchComments"] => string(0) ""
["estimatedTotalResultsCount"]=> int(144000000)
["estimateIsExact"] => bool(false)
["resultElements"] => array(10) {
....
[1] => object(stdClass)#5 (9) {
["summary"] => string(0) ""
["URL"]=> string(60) "http://www.amazon.com/Pro-PHP-XML-Web-Services/dp/1590596331"
["snippet"] => string(116) "Amazon.com: Pro <b>PHP XML</b> and <b>Web Services</b> (Pro):
<b>Books</b>: Robert Richards by Robert<br> Richards."
["title"] => string(91) "Amazon.com: Pro <b>PHP XML</b> and <b>Web Services</b> (Pro):
<b>Books</b>: Robert Richards"
["cachedSize"] => string(3) "111k"
["relatedInformationPresent"] => bool(true)
["hostName"] => string(0) ""
["directoryCategory"] => object(stdClass)#6 (2) {
["fullViewableName"] => string(0) ""
["specialEncoding"] => string(0) ""
}
["directoryTitle"] => string(0) ""
}
....
}
SOAP: Client Headers
soap/headers.php
soapHeader::__construct ( string namespace, string name [,
mixed data [, bool mustUnderstand [, mixed actor]]] )
<?php
/* Create and authentication object with username/password */
class authentication {
public $username;
public $password;
}
$auth = new authentication();
$auth->username = 'MyUsername';
$auth->password = 'MyPassword';
/* You MUST encode the object */
$authVar = new SoapVar($auth, SOAP_ENC_OBJECT);
$header = new SoapHeader('urn:ExampleAPI', "Authentication",
$authVar, TRUE, SOAP_ACTOR_NEXT);
/* Set the new headers to use when creating SOAP messages */
$sClient->__setSoapHeaders(array($header));
?>
SOAP: Client Headers
<SOAP-ENV:Envelope xmlns:ns2="urn:ExampleAPI" ...">
<SOAP-ENV:Header>
<ns2:Authentication SOAP-ENV:mustUnderstand="1"
SOAP-ENV:actor="http://schemas.xmlsoap.org/soap/actor/next">
<ussername>MyUsername</username>
<password>MyPassword</password>
</ns2:Authentication>
</SOAP-ENV:Header>
.....
SOAP: Client Request Modification
soap/google/request_modification.php
require('google_key.php');
Class mySoapClient extends SoapClient {
function __doRequest($request, $location, $action, $version) {
/* Load the request into a DOMDocument */
$dom = new DOMDocument();
$dom->loadXML($request);
/* Find the url element and set url to http://www.php.net/ */
$nodeList = $dom->getElementsByTagName('url');
if ($nodeList->length == 1) {
$nodeList->item(0)->firstChild->nodeValue = "http://www.php.net/";
}
}
}
/* Serialize the tree and send modified request to parent method */
$request = $dom->saveXML();
return parent::__doRequest($request, $location, $action, $version);
$sClient = new mySoapClient('GoogleSearch.wsdl');
$cached = $sClient->doGetCachedPage($key, 'http://www.google.com/');
echo substr($cached, 1500, 700)."\n";
SOAP: Client Request Modification
RESULTS (soap/google/request_modification.php)
ont><br><br><center><font size=-2><i>Google is neither affiliated with the
authors of this page nor responsible for its
content.</i></font></center></td></tr></table></td></tr></table>
<hr>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>PHP: Hypertext Preprocessor</title>
<link rel="stylesheet" href="http://static.php.net/www.php.net/style.css" />
<link rel="stylesheet" href="http://static.php.net/www.php.net/styles/phpnet.css"
/>
<link rel="shortcut icon" href="http://static.php.net/www.php.net/favicon.ico" />
<link rel="alternate" type="application/rss+xml" title="PHP: Hypertext
Preprocessor" href="http://www.php.net/news.rss" />
<script language
SOAP: Debugging Client Requests
soap/google/debug_client.php
/* Empty key so function will throw SoapFault */
$key = "";
$client_options = array ('trace'=>1);
try {
/* Create the Soap Client with debug option */
$client = new SoapClient('http://api.google.com/GoogleSearch.wsdl',
$client_options);
$cached = $client->doGetCachedPage($key, 'http://www.google.com/');
} catch (SoapFault $e) {
print "Last Request Headers: \n".$client->__getLastRequestHeaders()."\n\n";
print "Last Request: \n".$client->__getLastRequest()."\n\n";
print "Last Response Headers: \n".$client->__getLastResponseHeaders()."\n\n";
print "Last Response: \n".$client->__getLastResponse()."\n";
}
SOAP: Debugging Client Request
RESULT
Last Request Headers:
POST /search/beta2 HTTP/1.1
Host: api.google.com
Connection: Keep-Alive
User-Agent: PHP SOAP 0.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "urn:GoogleSearchAction"
Content-Length: 554
Last Request:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="urn:GoogleSearch" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAPENC="http://schemas.xmlsoap.org/soap/encoding/" SOAPENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAPENV:Body><ns1:doGetCachedPage><key xsi:type="xsd:string"></key><url
xsi:type="xsd:string">http://www.google.com/</url></ns1:doGetCachedPage></SOA
P-ENV:Body></SOAP-ENV:Envelope>
SOAP: Debugging Client Requests
RESULT Continued
Last Response Headers:
POST /search/beta2 HTTP/1.1
Host: api.google.com
Connection: Keep-Alive
User-Agent: PHP SOAP 0.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "urn:GoogleSearchAction"
Content-Length: 554
Last Response:
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring>Exception from service object: Invalid authorization key: </faultstring>
<faultactor>/search/beta2</faultactor>
<detail>
<stackTrace>com.google.soap.search.GoogleSearchFault: Invalid authorization key:
at com.google.soap.search.QueryLimits.lookUpAndLoadFromINSIfNeedBe(QueryLimits.java:213)
. . .</stackTrace>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
SOAP: Client and Document/Literal
soap/docliteral/amazonec_funcs.php
<?php
$wsdl =
"http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl";
try {
/* Create the Soap Client to an Amazon ECS Search */
$client = new SoapClient($wsdl);
print "Functions: \n";
$functions = $client->__getFunctions();
foreach($functions AS $function) {
echo $function."\n";
}
print "\nTypes: \n";
$types = $client->__getTypes();
foreach($types AS $type) {
echo $type."\n\n";
}
} catch (SoapFault $e) {
var_dump($e);
}
?>
SOAP: Client and Document/Literal
RESULTS (soap/docliteral/amazonec_ funcs.php)
Functions:
HelpResponse Help(Help $body)
ItemSearchResponse ItemSearch(ItemSearch $body)
ItemLookupResponse ItemLookup(ItemLookup $body)
BrowseNodeLookupResponse BrowseNodeLookup(BrowseNodeLookup $body)
…
Types:
struct ItemLookup {
string MarketplaceDomain;
string AWSAccessKeyId;
ItemLookupRequest Shared;
ItemLookupRequest Request;
}
struct ItemLookupResponse {
OperationRequest OperationRequest;
Items Items;
}
struct ItemLookupRequest {
string MerchantId;
positiveInteger OfferPage;
string ItemId;
string SearchInsideKeywords;
}
struct Items {
Request Request;
nonNegativeInteger TotalResults;
nonNegativeInteger TotalPages;
Item Item;
}
SOAP: Client and Document/Literal
soap/docliteral/ amazonec.php
class ItemLookupRequest {
public $ItemId;
}
class ItemLookup {
public $MarketplaceDomain;
public $AWSAccessKeyId;
public $Request;
}
$AWSAccessKeyId = '<Your AWS Access Key >;
$wsdl = "http://webservices.amazon.com/AWSECommerceService/AWSECommerceService.wsdl";
$client = new SoapClient($wsdl);
$params = new ItemLookup();
$params->AWSAccessKeyId = $AWSAccessKeyId
$params->Request = new ItemLookupRequest();
$params->Request->ItemId = '1590596331';
$results = $client->ItemLookup($params);
var_dump($results);
SOAP: Client and Document/Literal
RESULTS (soap/docliteral/ amazonec.php)
. . .
["Item"]=>
object(stdClass)#13 (3) {
["ASIN"]=>string(10) "1590596331"
["DetailPageURL"]=>string(173)
"http://www.amazon.com/gp/redirect.html%3FASIN=159059
6331%26tag=ws%26lcode=sp1%26cID=2025%26ccmID=165953%2
6location=/o/ASIN/1590596331%253FSubscriptionId=0MWT9
W26N2NFGGJZ33R2"
["ItemAttributes"]=>
object(stdClass)#14 (4) {
["Author"]=>string(15) "Robert Richards"
["Manufacturer"]=>string(6) "Apress"
["ProductGroup"]=>string(4) "Book"
["Title"]=>string(34) "Pro PHP XML and Web
Services (Pro)"
}
SOAP: Server WSDL (using Document/Literal)
soap/server/exampleapi.wsdl
<xsd:element name="getPeopleByFirstLastName">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="first" type="xsd:string"/>
<xsd:element name="last" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="Person">
<xsd:all>
<xsd:element name="id" type="xsd:int"/>
<xsd:element name="lastName" type="xsd:string"/>
<xsd:element name="firstName" type="xsd:string"/>
</xsd:all>
</xsd:complexType>
<xsd:element name="getPeopleByFirstLastNameResponse" type="tns:ArrayOfPerson"/>
<message name="getPeopleByFirstLastName">
<part name="parameters" element="tns:getPeopleByFirstLastName"/>
</message>
<message name="getPeopleByFirstLastNameResponse">
<part name="result" element="tns:getPeopleByFirstLastNameResponse"/>
</message>
SOAP: Server
soap/server/soap_server.php
<?php
/* System status - TRUE indicates normal operation /
FALSE indicates down for maintenance */
$SYS_STATUS = TRUE;
function findPeople($firstName, $lastName) {
/* Initialize the Person Records */
$matching = array();
$people = array(array('id'=>1, 'firstName'=>'John', 'lastName'=>'Smith'),
array('id'=>2, 'firstName'=>'Jane', 'lastName'=>'Doe'));
foreach($people AS $person) {
/* Check if match on first name */
if (empty($firstSearch) || preg_match('/^'.$firstSearch.'$/i', $person['firstName'])) {
/* Check if match on last name */
if (empty($lastSearch) || preg_match('/^'.$lastSearch.'$/i', $person['lastName'])) {
$matching[ ] = $person;
}
}
}
return $matching;
}
SOAP: Server
soap/server/soap_server.php
function getPeopleByFirstLastName($getPeopleByFirstLastName) {
/* If system is down throw SOAP fault */
if (isset($GLOBALS['SYS_STATUS']) && $GLOBALS['SYS_STATUS'] == FALSE) {
$details = array("SysMessage"=>"Sys Error", "RetryInMinutes"=>60);
/* SoapFault::__construct ( string faultcode, string faultstring [, string faultactor [,
mixed detail [, string faultname [, SoapHeader headerfault]]]] ) */
}
throw new SoapFault("SYSError", "System Unavailable", "urn:ExampleAPI",
$details, "sysmaint");
$firstSearch = str_replace('*', '([a-z]*)', $getPeopleByFirstLastName->first);
$lastSearch = str_replace('*', '([a-z]*)', $getPeopleByFirstLastName->last);
$retval = array();
$results = findPeople($firstSearch, $lastSearch);
}
foreach($results AS $result) {
/* Add matching records as an encoded SoapVar */
$retval[] = new SoapVar($result, SOAP_ENC_ARRAY, "Person", "urn:ExampleAPI");
}
return $retval;
SOAP: Server
soap/server/soap_server.php
/* Create the server using WSDL and specify the actor URI */
$sServer = new SoapServer("exampleapi.wsdl", array('actor'=>'urn:ExampleAPI'));
/* Register the getPeopleByFirstLastName function */
$sServer->addFunction("getPeopleByFirstLastName");
/* Handle the SOAP request */
$sServer->handle();
?>
SOAP: Calling our Server
soap/server/soap_client.php
<?php
try {
$sClient = new SoapClient('exampleapi.wsdl');
/* Set search parameters */
$params = array('first'=>'jo*', 'last'=>'*');
/* Make request and dump response */
$response = $sClient->getPeopleByFirstLastName($params);
var_dump($response);
} catch (SoapFault $e) {
/* Dump any caught SoapFault exceptions */
var_dump($e);
}
?>
SOAP: Calling our Server
RESULTS
array(1) {
[0]=>
object(stdClass)#2 (3) {
["id"]=>
int(1)
["lastName"]=>
string(5) "Smith"
["firstName"]=>
string(4) "John"
}
}
SOAP: Modifying A Request
$dom = DOMDocument::loadXML(file_get_contents('php://input'));
$soapNS = $dom->documentElement->namespaceURI;
$soapXPath = new DOMXPath($doc);
$soapXPath ->registerNamespace('wssoap', $soapNS);
$soapXPath ->registerNamespace('wssephp',
'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd');
$headers = $soapXPath->query('//wssoap:Envelope/wssoap:Header/wssephp:Security');
if ($security = $headers->item(0)) {
$actor = $security->getAttributeNS($soapNS, 'actor');
/* Code here first checks to make sure actor is correct for this header */
if (/* actor is empty or actor specifies this server */) {
/* Handle WS-Security and then remove Security element */
$security->parentElement->removeChild($security);
}
}
/* If Header is empty it also can be removed */
/* Process request */
$soapServer->handle($dom->saveXML());
Questions?