5

I have a WSDL that has an element that requires an attribute:

<xsd:complexType name="claim">
  <xsd:annotation>
    <xsd:documentation>Claim Element</xsd:documentation>
  </xsd:annotation>
  <xsd:sequence>
    <!-- other elements removed -->
  </xsd:sequence>
  <xsd:attribute name="claimId" type="xsd:nonNegativeInteger" use="required" />
</xsd:complexType>

In terms of generated xml, it should look like:

<claims>
    <claim claimId="1">
        <!-- elements removed -->
    </claim>
    <!-- more claims -->
</claims>

Within a foreach loop I am putting together an array of elements and using the attribute as part of the key:

//$claim = array of key/value pairs
$claim = [...];
$claim = new \SoapVar($claim, SOAP_ENC_OBJECT, null, null, 'claim claimId="' . ($key+1) . '"');
$claims['claim claimId="'.($key+1).'"'] = $claim;

When it comes to passing this to the SoapClient, the elements get removed:

//$client = new \SoapClient($wsdl);
$client->checkClaims($claims);

But all I'm getting is:

<claims />

How do I get my soap client to parse the claim elements correctly in the soap call?

2
  • Can you show code with sample values added to it? Commented Jun 25, 2018 at 6:49
  • can you post a dummy Commented Jun 28, 2018 at 10:15

2 Answers 2

2

So there are few issues with your code. For this to work, you need to use the SoapClient in WSDL mode ($client = new \SoapClient($wsdl);, which you are doing). Next below is wrong

$claim = [...];
$claim = new \SoapVar($claim, SOAP_ENC_OBJECT, null, null, 'claim claimId="' . ($key+1) . '"');
$claims['claim claimId="'.($key+1).'"'] = $claim;

You don't add attributes using 'claim claimId="' . ($key+1) . '"'.

Now what you need is to use a classmap. Below is a sample python flask app I created to show the WSDL

from flask import Flask

app  = Flask(__name__)

@app.route("/ICalculator",methods=['get', 'post'])
def reply():
    return "<xmldata />"

@app.route("/app.wsdl")
def send():
    return """<wsdl:definitions
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
  xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
  xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
  xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
  xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract"
  xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl"
  xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
  xmlns:wsa10="http://www.w3.org/2005/08/addressing"
  xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex" targetNamespace="http://localhost:5001"
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
  <wsdl:types>
    <xsd:schema targetNamespace="http://localhost:5001" elementFormDefault="qualified" >
    <xsd:element name="Claim">
    <xsd:complexType>
        <xsd:sequence>
        <xsd:element minOccurs="0" name="a" type="xsd:int" />
        <xsd:element minOccurs="0" name="b" type="xsd:int" />
      </xsd:sequence>
        <xsd:attribute name="claimId" type="xsd:nonNegativeInteger" use="required" />
    </xsd:complexType>
  </xsd:element>
  <xsd:element name="AddResponse">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element minOccurs="0" name="result" type="xsd:int" />
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
</xsd:schema>
  </wsdl:types>
  <wsdl:message name="ICalculator_Add_InputMessage">
    <wsdl:part name="parameters" element="tns:Add" />
  </wsdl:message>
  <wsdl:message name="ICalculator_Add_OutputMessage">
    <wsdl:part name="parameters" element="tns:AddResponse" />
  </wsdl:message>
  <wsdl:portType name="ICalculator">
    <wsdl:operation name="Add">
      <wsdl:input wsaw:Action="http://localhost:5001/ICalculator/Add" message="tns:ICalculator_Add_InputMessage" />
      <wsdl:output wsaw:Action="http://localhost:5001/ICalculator/AddResponse" message="tns:ICalculator_Add_OutputMessage" />
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="DefaultBinding_ICalculator" type="tns:ICalculator">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="Add">
      <soap:operation soapAction="http://localhost:5001/ICalculator/Add" style="document" />
      <wsdl:input>
        <soap:body use="literal" />
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal" />
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="CalculatorService">
        <wsdl:port name="ICalculator" binding="tns:DefaultBinding_ICalculator">
            <soap:address location="http://localhost:5001/ICalculator" /></wsdl:port>
</wsdl:service>
</wsdl:definitions>
""".replace(r"\r", "").replace(r"\n", "")


if __name__ == "__main__":
   app.run(debug=True, host='0.0.0.0', port=5000)

Then ran the same using

python3 wsdl.py

And ran another socat to view the traffic

socat -v TCP-LISTEN:5001,fork TCP:127.0.0.1:5000

Next I wrote a sample PHP code to show how classmap works

<?php
class Claim {
  public function __construct(Array $properties=array()){
      foreach($properties as $key => $value){
        $this->{$key} = $value;
      }
    }
}

$test = new Claim(array('claimId'=>10, 'a'=> 22, 'b'=> 33));

$claim=new SoapVar($test, SOAP_ENC_OBJECT);
$wsdl = "http://localhost:5001/app.wsdl";
$client = new SoapClient($wsdl, array(
    'trace'        => 1,
    'encoding'     => 'UTF-8',
    'soap_version' => SOAP_1_1,
    'classmap'     => array('Claim' => 'Claim')
));
$client->add($claim);

And the resultant xml is

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://localhost:5001" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><SOAP-ENV:Body><parameters claimId="10" xsi:type="ns1:Claim"><ns1:a>22</ns1:a><ns1:b>33</ns1:b></parameters></SOAP-ENV:Body></SOAP-ENV:Envelope>

Data as attributes

References:

PHP soap request with an element attribute and child elements

PHP SoapVar Object Attribute?

Adding attributes to the actual function tag in PHP soapCall

How do I add additional attributes to XML Elements with the SoapClient Class in PHP

php SoapVar not setting attributes

Getting the XML as string for a SoapVar variable - without a webservice (locally)?

Getting the XML as string for a SoapVar variable - without a webservice (locally)?

http://fvue.nl/wiki/Php:_Soap:_How_to_add_attribute_to_SoapVar

https://forums.phpfreaks.com/topic/137357-solved-php-soap-client-node-attributes/

http://eosrei.net/articles/2012/01/php-soap-xml-attributes-namespaces-xmlwriter

Sign up to request clarification or add additional context in comments.

2 Comments

I've tried something like this already, using the Wsdl2PhpGenerator. I learned that the soap server is very literal in what it expects. The generated xml has to be as I posted.
So what is the issue my approach?
0

Knowing that \SoapClient can accept an xml string, I decided to convert my array of data into an xml string:

/**
 * Convert an array to an xml. Recursive function.
 * @param array $array
 * @param String $rootElement OPTIONAL name of root element.
 * @param \Simple XMLElement $xml OPTIONAL
 * @return String
 */
function arrayToXml($array, $rootElement = null, $xml = null) {
    $_xml = $xml;

    if ($_xml === null) {
        $_xml = new \SimpleXMLElement($rootElement !== null ? $rootElement : '<root/>');
    }

    foreach ($array as $k => $v) {
        if (is_array($v)) { //nested array
            arrayToXml($v, $k, $_xml->addChild($k));
      } else {
            $_xml->addChild($k, $v);
      }
    }
    //Remove xml doctype and root name spaces.
    return str_replace(["<?xml version=\"1.0\"?>\n", '<root>', "</root>\n"], '', $_xml->asXML());
}

//my array of claims
$claims = [
    'claim claimId="1"' => [
        //...
    ],
    'claim claimId="2"' => [
        //...
    ],
    //...
];
$claims = arrayToXml($claims);

This generates an xml string of:

<claim claimId="1"><!-- stuff --></claim claimId="1"><claim claimId="2"><!-- stuff --></claim claimId="2">

Next step is to remove the attribute from the closing tags:

$claims = preg_replace('#</claim claimId="\d+">#', '</claim>', $claims);

Finally, I wrap my xml in the appropriate parameter name and convert it to a \SoapVar so the \SoapClient will parse it correctly:

$claims = '<ns1:claims>' . $claims . '</ns1:claims>';
$claims = new \SoapVar($claims, XSD_ANYXML, 'http://www.w3.org/2001/XMLSchema-instance');

Passing $claims into my \SoapClient and running var_dump($client->__getLastRequest()); shows my generated xml as:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://www.w3.org/2001/XMLSchema-instance"><SOAP-ENV:Body><ns1:claims><claim claimId="1"><!--stuff--></claim><claim claimId="2"><!--stuff--></claim></ns1:claims></SOAP-ENV:Body></SOAP-ENV:Envelope>

Just as it wanted.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.