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?
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>
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
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.