I am setting up a SOAP web service, that should return a composite message.
A valid instance of this message would be the following:
<dl190Response xmlns="http://pse/">
<cdhead cisprik="5563167"/>
<mvts>
<mvts_S att="a1">
<x>x1</x>
<w>w1</w>
</mvts_S>
<mvts_S>
<x>x2</x>
<w>w2</w>
</mvts_S>
</mvts>
</dl190Response>
All this is neatly defined in the wsdl:
<?xml version="1.0" encoding="UTF-8"?>
<definitions
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://pse/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
name="PSE"
targetNamespace="http://pse/">
<types>
<xs:schema xmlns="http://pse/" targetNamespace="http://pse/">
<xs:complexType name="cdhead_T">
<xs:attribute name="cisprik" type="xs:long"/>
</xs:complexType>
<xs:complexType name="mvts_T">
<xs:sequence>
<xs:element name="mvts_S" type="mvts_S_T" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="mvts_S_T">
<xs:sequence>
<xs:element name="x" type="xs:string"/>
<xs:element name="w" type="xs:string"/>
</xs:sequence>
<xs:attribute name="att" type="xs:string" use="optional"/>
</xs:complexType>
</xs:schema>
</types>
<message name="DL190Req">
<part name="cdhead" type="tns:cdhead_T"/>
</message>
<message name="DL190Res">
<part name="cdhead" type="tns:cdhead_T"/>
<part name="mvts" type="tns:mvts_T"/>
</message>
<portType name="DLPortType">
<operation name="dl190">
<input message="tns:DL190Req"/>
<output message="tns:DL190Res"/>
</operation>
</portType>
<binding name="DLBinding" type="tns:DLPortType">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="dl190">
<soap:operation soapAction="http://www.testServer.com/test_soap.php#dl190"/>
<input>
<soap:body use="literal" namespace="http://pse/"/>
</input>
<output>
<soap:body use="literal" namespace="http://pse/"/>
</output>
</operation>
</binding>
<service name="PSE">
<port name="DLPortType" binding="tns:DLBinding">
<soap:address location="http://www.testServer.com/test_soap.php"/>
</port>
</service>
</definitions>
I have been working on the server side test_soap.php endlessly to get it right, but I don’t succeed.
Part of what is working properly up to the point of returning the XML is as follows:
<?php
class PSE {
function dl190 ($arg) {
//I don't need to extract the input data just now
mysql_connect('127.0.0.1:3306', 'user', 'password');
mysql_select_db('myDatabase');
$xml = new SimpleXMLElement('<dl190Res/>');
$xml -> addChild('cdhead');
$mvts = $xml -> addChild('mvts');
$rows = mysql_query('select * from trx');
while($data = mysql_fetch_assoc($rows)) {
$mvts_S = $mvts -> addChild('mvts_S');
foreach($data as $key => $value) {
if ($key == 'att') { $mvts_S -> addAttribute($key, $value);}
else {$mvts_S -> addChild($key, $value);}
}
};
$dom = dom_import_simplexml ($xml) -> ownerDocument;
// now respond to the request and return the XML
}
};
ini_set( "soap.wsdl_cache_enabled", "0");
$server = new SoapServer ("test.wsdl");
$server -> setClass ('PSE');
$server -> setObject (new PSE());
$server -> handle();
?>
I tried virtually everything I could think of to get the response all right, but I did not succeed. I was able to do the same for a message containing just one part earlier (see my most recent question+answer).
But here, with two message parts, I don’t succeed.
Debug of the $xml contents show that it is exactly what I wish to see returned, after letting the soap server wrap it into Envelope+Body of course.
Actually the situation is different from the one with only one message part: there I could create a new SoapVar from the one part as long as I stripped off the XML declaration first, and return that. Here I cannot do the same, because the return value consists of two parts.
So I wonder which of the following I should do now:
- declare a class for the response message and populate and return that
- perform some magic with SoapVar and/or SoapParam (mind you, I tried a lot of that already)
- perform some magic with arrays and SoapVar (tried a lot of that too already)
- somehow (how?) ask the wsdl for help
- something completely different
- quit this entire nightmare with SoapServer and create my own http response from scratch
I appreciate all help with this, so all ye soap experts, don’t hesitate to try to answer this question!
ADDITION
As a temp workaround, I edited the WSDL, changing the response message to having one part only. This allowed me to pass the expected message as concatenation of the two parts expected (or any other message for that matter, as no message defined structure WSDL check is being done by SoapVar on the returned value):
$xml1 = new SimpleXMLElement('<cdhead/>');
$xml1 -> addAttribute ('xmlns', 'http://pse/');
$xml1 -> addAttribute ('cisprik', $newCisprik);
$xml2 = new SimpleXMLElement('<mvts/>');
$rows = mysql_query('select * from trx');
while($data = mysql_fetch_assoc($rows)) {
$mvts_S = $xml2 -> addChild('mvts_S');
foreach($data as $key => $value) {
if ($key == 'att') { $mvts_S -> addAttribute($key, $value);}
else {$mvts_S -> addChild($key, $value);}
}
};
$dom1 = dom_import_simplexml ($xml1) -> ownerDocument;
$dom2 = dom_import_simplexml ($xml2) -> ownerDocument;
$part1 = $dom1 -> saveXML($dom1 -> documentElement);
$part2 = $dom2 -> saveXML($dom2 -> documentElement);
$result = new SoapVar ($part1 . $part2, XSD_ANYXML);
Peculiar about this is that the concatenation is not valid XML of course, lacking the surrounding root element, but SoapVar is able to parse it anyhow.
So there it is: can anyone with detailed insight in SoapVar and SoapParam / SoapServer explain whether it is at all possible to return two message parts?
And explain how to do so?
Or, alternatively, supply detailed information on how to do so in other SOAP setups?
I tried and setup your minimal SoapServer, here’s what I did:
Here is my calling request:
It did not work initially because of your calls to the database, but I understand you really only need a solution of how to respond properly on the soap layer, you’ll figure out the rest.
Here is the simple solution:
Note that PHP basically only emits a mixture of stdClass and array in the request argument (I dumped what you get as a comment at the top). This is a sad thing, but I believe it is a fair thing to respond on the same level and not make things worse by using XML for the way back.
If you execute the above request against this code, you’ll get this soap response:
There is however room for improvement. The PHP SoapServer (and SoapClient as well) have a feature called classmap, and I highly recommend that you use it. If your IDE supports any type of PHPDoc autocompletion, you will take advantage of it nearly everywhere that deals with setting values properly.
Here is my version with a classmap definition. Note that I prefixed them all with “PSE” to highlight the fact that the classnames do not need to be named after the complexTypes in your WSDL.
Unfortunately, one annoying point is not resolved: In your response, you cannot use a class, but have to use an array without any hints of which index parameter is mapped to which xml result. This is really bad, but to change that you’ll have to change the WSDL.
I’m unhappy to report that I am no expert in creating WSDL files. I tried to add a complex type as the only element in the response. If you look at the dump in my second version, you see that you get a class PSE_cdhead_T, which is the mapped complexType of the only part of the request message.
Because the response message has two parts, the SoapServer has to get them inside an array. There is no named referencing possible. I suggest you add a new complexType here and create a new class accordingly in the map, like this:
Then you can prepare the response more easily:
This most likely will result in a change to your XML response – I cannot assess the impact, though.
One final thought: There are some WSDL code generators for PHP out there, which you might try. They will generate the classes you need for the classmap automatically. Last time I tried them, they seemed to work, but not with all WSDL files I tested. The Soap definition seems to be too complex to get this right. But if it works, it’s well worth it instead of manually creating them.