Tim’s Weblog Tim's Weblog
Tim Strehle’s links and thoughts on Web apps, managing software development and Digital Asset Management, since 2002.
2006-08-28

NOT Getting Started with PHP 5 SOAP

Not having played with PHP 5's native SOAP extension yet, I did expect it to work smoothly with the most simple application I could think of - querying Google via its SOAP Search API. Well...

I first compiled the latest PHP 5.1.6 with --enable-soap and downloaded the Google SOAP Search API developer's kit which contains their WSDL file, GoogleSearch.wsdl.

Running this example PHP code...

<?php $client = new SoapClient('GoogleSearch.wsdl'); try { $result = $client->doGoogleSearch( '[Secret Google key]', 'Tim Strehle', 0, 3 ); foreach ($result->resultElements as $resultElement) { print $resultElement->URL; } } catch (SOAPFault $f) { echo $f->faultstring . "\n"; } ?>

... produced a lovely error message:

tim@vm:/tmp>php test.php No Deserializer found to deserialize a ':filter' using encoding style 'http://schemas.xmlsoap.org/soap/encoding/'.

Here's the actual SOAP request PHP was sending:

<?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:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:doGoogleSearch> <key xsi:type="xsd:string">[Secret Google key]</key> <q xsi:type="xsd:string">Tim Strehle</q> <start xsi:type="xsd:int">0</start> <maxResults xsi:type="xsd:int">3</maxResults> <filter xsi:nil="true"/> <restrict xsi:nil="true"/> <safeSearch xsi:nil="true"/> <lr xsi:nil="true"/> <ie xsi:nil="true"/> <oe xsi:nil="true"/> </ns1:doGoogleSearch> </SOAP-ENV:Body> </SOAP-ENV:Envelope>

The error message did come from Google:

<?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:Client</faultcode> <faultstring>No Deserializer found to deserialize a ':filter' using encoding style 'http://schemas.xmlsoap.org/soap/encoding/'.</faultstring> <faultactor>/search/beta2</faultactor> </SOAP-ENV:Fault> </SOAP-ENV:Body> </SOAP-ENV:Envelope>

Obviously Google doesn't like the xsi:nil stuff created by PHP. Modifying those empty tags manually in an XML file...

<?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:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:doGoogleSearch> <key xsi:type="xsd:string">[Secret Google key]</key> <q xsi:type="xsd:string">Tim Strehle</q> <start xsi:type="xsd:int">0</start> <maxResults xsi:type="xsd:int">3</maxResults> <filter xsi:type="xsd:boolean">false</filter> <restrict xsi:type="xsd:string"></restrict> <safeSearch xsi:type="xsd:boolean">false</safeSearch> <lr xsi:type="xsd:string"></lr> <ie xsi:type="xsd:string"></ie> <oe xsi:type="xsd:string"></oe> </ns1:doGoogleSearch> </SOAP-ENV:Body> </SOAP-ENV:Envelope>

... and sending the SOAP request using curl finally produced correct results:

tim@vm:/tmp>cat test.curl header = "SOAPAction: urn:GoogleSearchAction" header = "Content-Type: text/xml" data = "@/tmp/test.xml" url = "http://api.google.com/search/beta2" tim@vm:/tmp>curl -K test.curl | xmllint --format - <?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> <ns1:doGoogleSearchResponse xmlns:ns1="urn:GoogleSearch" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <return xsi:type="ns1:GoogleSearchResult"> <directoryCategories xmlns:ns2="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns2:Array" ns2:arrayType="ns1:DirectoryCategory[0]"> </directoryCategories> <documentFiltering xsi:type="xsd:boolean">false</documentFiltering> <endIndex xsi:type="xsd:int">3</endIndex> <estimateIsExact xsi:type="xsd:boolean">false</estimateIsExact> <estimatedTotalResultsCount xsi:type="xsd:int">184000</estimatedTotalResultsCount> <resultElements xmlns:ns3="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns3:Array" ns3:arrayType="ns1:ResultElement[3]"> <item xsi:type="ns1:ResultElement"> <URL xsi:type="xsd:string">http://tim.digicol.de/</URL> <cachedSize xsi:type="xsd:string">4k</cachedSize> <directoryCategory xsi:type="ns1:DirectoryCategory"> <fullViewableName xsi:type="xsd:string"/> <specialEncoding xsi:type="xsd:string"/> </directoryCategory> <directoryTitle xsi:type="xsd:string"/> <hostName xsi:type="xsd:string"/> <relatedInformationPresent xsi:type="xsd:boolean">true</relatedInformationPresent> <snippet xsi:type="xsd:string">&lt;b&gt;Tim&lt;/b&gt; at his desk &lt;b&gt;Tim Strehle&lt;/b&gt; @ Digital Collections &lt;b&gt;...&lt;/b&gt; &lt;b&gt;Tim&amp;#39;s&lt;/b&gt; Weblog &amp;middot; Ceterum censeo...&lt;br&gt; www.liederdatenbank.de, my personal project for building a database &lt;b&gt;...&lt;/b&gt;</snippet> <summary xsi:type="xsd:string"/> <title xsi:type="xsd:string">&lt;b&gt;Tim Strehle&lt;/b&gt; @ Digital Collections</title> </item> <item xsi:type="ns1:ResultElement"> <URL xsi:type="xsd:string">http://tim.digicol.de/weblog/</URL> <cachedSize xsi:type="xsd:string">33k</cachedSize> <directoryCategory xsi:type="ns1:DirectoryCategory"> <fullViewableName xsi:type="xsd:string"/> <specialEncoding xsi:type="xsd:string"/> </directoryCategory> <directoryTitle xsi:type="xsd:string"/> <hostName xsi:type="xsd:string"/> <relatedInformationPresent xsi:type="xsd:boolean">true</relatedInformationPresent> <snippet xsi:type="xsd:string">My linkblog: What I (&lt;b&gt;Tim Strehle&lt;/b&gt;) read on the web, on PHP. XML. Information Science&lt;br&gt; and Information Architecture... 2006-08-23 &lt;b&gt;...&lt;/b&gt;</snippet> <summary xsi:type="xsd:string"/> <title xsi:type="xsd:string">&lt;b&gt;Tim&amp;#39;s&lt;/b&gt; Weblog » Latest posts</title> </item> <item xsi:type="ns1:ResultElement"> <URL xsi:type="xsd:string">http://freshmeat.net/~tistre/</URL> <cachedSize xsi:type="xsd:string">12k</cachedSize> <directoryCategory xsi:type="ns1:DirectoryCategory"> <fullViewableName xsi:type="xsd:string"/> <specialEncoding xsi:type="xsd:string"/> </directoryCategory> <directoryTitle xsi:type="xsd:string"/> <hostName xsi:type="xsd:string"/> <relatedInformationPresent xsi:type="xsd:boolean">true</relatedInformationPresent> <snippet xsi:type="xsd:string">User info page for &lt;b&gt;Tim Strehle&lt;/b&gt;. Name: &lt;b&gt;Tim Strehle&lt;/b&gt;. User ID: #96744. Email: &lt;b&gt;tim&lt;/b&gt;&lt;br&gt; __at__ &lt;b&gt;strehle&lt;/b&gt; __dot__ &lt;b&gt;...&lt;/b&gt; &lt;b&gt;Tim Strehle&lt;/b&gt; didn&amp;#39;t post any article comments yet. &lt;b&gt;...&lt;/b&gt;</snippet> <summary xsi:type="xsd:string"/> <title xsi:type="xsd:string">freshmeat.net: User information</title> </item> </resultElements> <searchComments xsi:type="xsd:string"/> <searchQuery xsi:type="xsd:string">Tim Strehle</searchQuery> <searchTime xsi:type="xsd:double">0.020699</searchTime> <searchTips xsi:type="xsd:string"/> <startIndex xsi:type="xsd:int">1</startIndex> </return> </ns1:doGoogleSearchResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>

I don't know enough SOAP so I cannot say whether this is Google's or PHP's fault, but it's definitely not the "just works" experience I'd expect from both of them. If SOAP's complexity itself is to blame, I may have been right to lean towards REST (or POX over HTTP) without ever having used much SOAP...

Update: "Optional fields cause problems", says Dare Obasanjo - ETech 2005 Trip Report: Building a New Web Service at Google... It turns out that filling in all optional parameters in the SOAP call makes the PHP script work:

<?php $client = new SoapClient('GoogleSearch.wsdl'); try { $result = $client->doGoogleSearch( '[Secret Google key]', 'Tim Strehle', 0, 3, false, '', false, '', '', '' ); ... ?>
Mon, 28 Aug 2006 22:24:28 +0000