Friday, February 04, 2005

Passing Array Data through PHP 5's SOAP Engine



Click here for AmazonI'd been looking hi and lo for an example of array passing using PHP 5's new SOAP engine. SOAP, for those of you non-techies who haven't passed out from boredom, is a standard protocol used for communicating from one system to another. Best of all, its underlying technologies are web-based, so devices like firewalls and routers usually don't interfere (too much) with these transactions.

There are positives and negatives to using an XML-based transport. The obvious one is size. XML is an incredibly wordy language (and that problem is being worked by a separate standards committee) when compared to a binary protocol like, say, ASN.1. With verbosity comes potential performance issues: namely, a lot of redundant data gets passed over the wire.

In addition, if you've got a couple of chatty systems, then you have the network overhead of TCP connections. TCP is the underlying IP protocol used by HTTP. TCP is a connection-oriented protocol, meaning packets have to be reassembled in the correct order on a destination machine. And the destination box may ask for retries if doesn't get a particular packet. UDP, a connectionless protocol, could have been used (that's what, say, voice-over-IP or VOIP uses)... but the firewall and router issue comes into play with UDP.

The bottom line is that there are some significant benefits associated with SOAP, even though the drawbacks are obvious.

So, how to overcome the drawbacks? Caching is one way. Let's say I wanted to transmit database data accumulating on one machine to another. I could do it rapidly - as fast as I could detect the tables changing. But the twin overhead issues of size and chattiness may hamstring the process. Caching helps address both issues.

I let, say, 30 seconds worth of changes accumulate in the cache and then *boom*, every half a minute I flush the cache to the other box via a single SOAP call. I now have a single envelope (rather than many) and a single TCP setup and teardown. Problem solved.

Problem was, I couldn't find a good example of array support compatible with PHP 5's new SOAP engine. It didn't seem to like the WSDL (definition of the service) files I'd copied from Java projects and such. Oh, it read them, it just didn't understand the complex array type.

Anyhow, for those of you googling for an example of how to pass array data through a SOAP call, here's a simple WSDL file, client and server that demonstrates how you might do this.

=== texter.wsdl ===
<?xml version='1.0' encoding='UTF-8'?>
<definitions name='texter'
  targetNamespace='http://127.0.0.1/texter'
  xmlns:tns=' http://127.0.0.1/texter '
  xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/'
  xmlns:xsd='http://www.w3.org/2001/XMLSchema'
  xmlns:soapenc='http://schemas.xmlsoap.org/soap/encoding/'
  xmlns:wsdl='http://schemas.xmlsoap.org/wsdl/'
  xmlns='http://schemas.xmlsoap.org/wsdl/'
  xmlns:xsd1='http://127.0.0.1/texter'>  

<types>
   <schema xmlns="http://www.w3.org/2001/XMLSchema"
      targetNamespace="http://127.0.0.1/texter"
         xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
         xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">

         <complexType name="ArrayOfString">
            <complexContent>
               <restriction base="soapenc:Array">
                  <attribute ref="soapenc:arrayType"
                  wsdl:arrayType="xsd:string[]"/>
               </restriction>
            </complexContent>
         </complexType>

  </schema>
</types>

<message name='texterRequest'>
  <part name='code' type='xsd:short'/>
  <part name='text' type='xsd:string'/>
  <part name='arra' type='xsd1:ArrayOfString'/>
</message>
<message name='texterResponse'>
  <part name='text' type='xsd:string'/>
</message>

<portType name='texterPortType'>
  <operation name='texter'>
    <input message='tns:texterRequest'/>
    <output message='tns:texterResponse'/>
  </operation>
</portType>

<binding name='texterBinding' type='tns:texterPortType'>
  <soap:binding style='rpc'
    transport='http://schemas.xmlsoap.org/soap/http'/>
  <operation name='texter'>
    <soap:operation soapAction='urn:xmethods-texting#texter'/>
    <input>
      <soap:body use='encoded' namespace='urn:xmethods-texting'
        encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
    </input>
    <output>
      <soap:body use='encoded' namespace='urn:xmethods-texting'
        encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
    </output>
  </operation>
</binding>

<service name='texterService'>
  <port name='texterPort' binding='texterBinding'>
    <soap:address location='http://127.0.0.1/texter.php'/>
  </port>
</service>
</definitions>
=== textclient.php ===
<html><body>
<?php
  $client = new soapclient("http://127.0.0.1/texter.wsdl");
  $aTemp = array(
    "This is a test",
    "This is, too",
    "So is this!"
  );
  print($client->texter(33, "This is a test message to be written", $aTemp));
?>
</body></html>

=== texter.php ===
<?php

//    Write an individual line of text.
//
function writeText($nCode, $text) {
    $r = 0;
    do {
        //
        $sTextFile = $_SERVER["DOCUMENT_ROOT"];
        $sTextFile .= "/text/";
        $sTextFile .= "file.txt";
        if (($f = fopen($sTextFile, "a")) === false) {
            $r = 1;
            break;
        }
        if (strstr($text, "\n") === false) {
            $text .= "\r\n";
        }
        $text = date("Y-m-d H:i:s ").$text;
        fwrite($f, $text);
        fclose($f);
    //
    } while (0);
    return ($r);
}

//    Text demo (demonstrates writing of the simple string
//        data type as well as the complex array to a file).
//
function texter($nCode, $sText, $aTemp) {
    $r = 0;
    do {
        //
        writeText($nCode, $sText);
        for ($i = 0; $i < sizeof($aTemp); $i++) {
            writeText($nCode, $aTemp[$i]);
        }
    //
    } while (0);
    return (($r) ? "Err" : "OK");
}

//
ini_set("soap.wsdl_cache_enabled", "0"); // disabling WSDL cache
$server = new SoapServer("texter.wsdl");
$server->addFunction("texter");
$server->handle();
//?>

No comments: