PHP SOAP XML attributes & namespaces with XMLWriter

If you've experienced the unending joy of attempting to make a PHP SOAP call using the SoapClass, you may have discovered XML node attributes cannot easily be created and passed. According to this comment: http://www.php.net/manual/en/soapvar.soapvar.php#101225 some people have had luck creating node attributes using the following code:

 
  $amount['_'] = 25; 
  $amount['currencyId'] = 'GBP'; 
  $encoded = new SoapVar($amount, SOAP_ENC_OBJECT); 

It has not worked on the three PHP installations I've tested it on, plus I needed to also specify namespaces. I decided I best go lower level, and looked into the SimpleXML class. Attributes are easy with SimpleXML, but attributes with namespaces aren't possible (Someone correct me if I am wrong.) So, I looked into the XMLWriter class and found exactly what I needed. The XMLWriter class gives you complete control over the XML creation, without have to write any XML yourself.

Here is the method I worked out for my use case and WSDL (code has been simplified, real thing is far more complex):

function call_soap_wsdl($method, $message, $operation, $important_id) {
  $xml = new XMLWriter();
  $xml->openMemory();
  $xml->startElementNS('objs', $method, 'ObjectNamespace');
    // Send NULL to not specify the NameSpace on every call.
    $xml->startElementNS('objs', $message, NULL);
      //Attribute on a Namespaced node!
      $xml->writeAttribute('node-attribute', $operation);
      $xml->startElementNS('objs', 'ImportantId', NULL);
        $xml->Text($important_id);
      $xml->endElement();
    $xml->endElement();
  $xml->endElement();

  //Convert it to a valid SoapVar
  $args = new SoapVar($xml->outputMemory(), XSD_ANYXML);
  
  $options = array(
    'trace' => TRUE,
    'soap_version' => SOAP_1_1,
  );
  
  $wsdl = 'http:// example.com/service.wsdl';
  $client = new SoapClient($wsdl, $options);  
  try {
    $result = $client->__SoapCall($method, array($args));
  }
  catch (Exception $e) {
    //Handle Exception - Use $client->__getLastRequestHeaders(), 
    //$client->__getLastRequest(), $client->__getLastResponse().
    $result = FALSE;
  }
  return $result;
}

If the above function is called using: call_soap_wsdl('Search', 'SearchObj', 'FindUser', 123);, the resulting SOAP request (printed using print $client->__getLastRequest()) is:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http ://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Body>
    <objs:Search xmlns:objs="ObjectNamespace">
      <objs:SearchObj operation="FindUser">
        <objs:ImportantId>123</objs:ImportantId>
      </objs:SearchObj>
    </objs:Search>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Comments

Helped me loads, thanks!

As you would if you would have to put an attribute in the Envelope tag?
E.g:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http ://schemas.xmlsoap.org/soap/envelope/" xmlns:tag="other namespace" xmlns:tag2="other namespace2">

good job buddy, but, if I want to make a xml document like this:

<node xmlns:"http:example.com">
</node>

instead of <ns1:node xmlns:"http://example.com">
because the xml required in my web service can't accept the tag </ns1 node>
do you understand me ? Any help will be very upseful.. thanks a lot.

I'd probably use SimpleXML instead then. It is simpler than XMLwriter, but doesn't support namespaces.

Thank god for this. Please comment on the php.net manual.

Thank you so much! This has doubtless saved people (including me) hundreds of hours.

By the way, in order to produce something like:

 <node xmlns:"http://example.com">
 </node>

instead of

<ns1:node xmlns:"http://example.com"/>

Something like the following worked for me:

$xml->startElementNS(null, 'node', 'http://example.com');

Can XMLwriter be used to create a SoapHeader with attributes?