I am struggling to get PHP's inbuilt SoapClient to interpret the response coming back from the web service I'm trying to call.
SoapUI is able to interrogate this soap method and return good results.
I am also able to get nusoap_client to return correct results (but am not able to use nusoap for other reasons and think I'm stuck with SoapClient).
Using SoapClient, I can see that seemingly good data is being returned, but instead of the results being parsed and broken into easily consumed arrays of values, the XML response string is being stuffed into a single field in an object (labelled 'any').
My code and results are shown below :
$client = new SoapClient($url);
$results = $client->GetPropertiesByProjectAndContractStatus($params);
var_dump($results);
The output from the above code is below :
object(stdClass)[3]
public 'GetListingsByGUIDResult' =>
object(stdClass)[4]
public 'any' => string '<xs:schema xmlns="" ........ (long xml here) ....
Now, perhaps it's possible that the service I am using is returning some xml which has something wrong with it (although it seems fine to my eye). nusoap and SoapUI both have no problems using it either.
So I'm wondering what is it with SoapClient that is different.
I have a function that grabs that result and turns it into a dom object so you can use the dom functions to extract data.
protected function getElementsFromResult($elementName, $simpleresult) {
$dom = new DOMDocument ();
$dom->preserveWhiteSpace = FALSE;
if ($simpleresult == null) {
echo 'null';
return null;
} else {
$dom->loadXML ( $simpleresult->any );
return $dom->getElementsByTagName ( $elementName );
}
$elementName is the name of the elements you want from the result and $simpleresult is the object containing the 'any' string.
This happens when the data returned is not specified in the WSDL that you are using. Anything not in the WSDL will get lumped into this "any" element at the end of parsing the XML.
If this is happening then you should ensure that your script is using the correct WSDL for the SOAP service you're using.
For example, if you're using an old WSDL and new elements are now in use in the service, they will end up inside this "any" element!
Did you try using the SOAP_SINGLE_ELEMENT_ARRAYS feature?
<?php
$client = new SoapClient($url, array('features' => SOAP_SINGLE_ELEMENT_ARRAYS));
Related
I have a PHP soap server (Using nuSoap), and a Java client (using Axis2). Which works pretty good, until it doesn't.
The gist of what I'm trying to do is send a code to the service, and return a XML list of file names.
<filename>20120413.zip</filename>
Here's the SSCE
<?
require_once('nusoap/lib/nusoap.php');
$server = new soap_server();
$server->configureWSDL('Download Database Backup', 'urn:downloadDatabase');
$server->register('getBackupFileNames', // method
array('herdCode' => 'xsd:string'), // input parameters
array('fileList' => 'xsd:string'), // output parameters
'urn:uploadDatabase', // namespace
'urn:uploadDatabase#uploadDatabase', // soapaction
'rpc', // style
'encoded', // use
'uploadDatabase' // documentation
);
function getBackupFileNames($herdCode)
{
$location = "/home/rhythms/backups/" . $herdCode;
$fileList = scandir($location);
return $fileList;
}//end function
$HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
$server->service($HTTP_RAW_POST_DATA);
?>
In a pinch, I know I could do a foreach and manually create the XML as a string. However it gets XMLEncoded then. Is there a better way? I would like to publish it by default in the WSDL. I've also tried the complexType but I had trouble handling that on the Axis2 side.
Thank you!
This isn't a direct answer. What I've come to is you can send a SOAP array using the SOAP-ARRAY complex data type. But it's not a very good method. Instead I'm going to investigate the native SOAP implementation that PHP provides.
Axis2 doesn't handle the SOAP-ARRAY complex datatype well, so I think it will be easier to adjust my implementation to PHP's native types.
This is left as a footnote so hopefully someone else won't fall down the same well I did as I was trying to find a good SOAP implementation.
I'm working on a project where I need to pass parameters to a .NET web service. Below is what the .NET SOAP request structure looks like.
<soap:Body>
<ListRequest xmlns="My.API.1.0">
<ID>guid</ID>
<Parameters>
<Values xmlns="api.test">
<fv ID="string">
<Value />
<ValueTranslation>string</ValueTranslation>
</fv>
<fv ID="string">
<Value />
<ValueTranslation>string</ValueTranslation>
</fv>
</Values>
</Parameters>
</ListRequest>
</soap:Body>
Below is part of the PHP code.
$params=array('ID'=> $listID,
'Parameters' =>
array(
array('ID' => 'ROOM', 'ValueTranslation' => '99999')));
$result=$soapClient->ListRequest($params);
Whenever I make the call without the parameter the request works and gets the results back to the php page. But when I introduce the parameters, I get "the function GETLIST expects parameter 'ROOM', which was not supplied" error. I've a feeling the the issue is the way I'm mapping the Parameter structure but I can't figure out how to map the SOAP xml above to a php code.
Thank you all for your help!
I don't think that you can specify parameters for a soap request using assoc arrays. Try using objects instead, it have worked for me in the past
$params = new stdClass();
$params->ID = $listId;
$params->Parameters = new stdClass();
$params->Parameters->Values = new stdClass();
$params->Parameters->Values->fv = array();
//Child of "Parameters"
$p = new stdClass();
$p->ID = 'ROOM';
$p->ValueTranslation = '99999';
$params->Parameters->Values->fv[] = $p;
$soap->ListRequest($params);
Another solution is to use an XML DOM and handle the creation of the XML data yourself (Then simply pass it as string to the function you want to call in SOAP)
Furthermore the datastructure that you make with the assoc array does not match the structure it should be.. (take a good look at the XML you are posting and you will see what you are missing)
Edit: Think i made something that should reflect your schema now.
I am trying to access a Web Service (SOAP) from a provider. I have not control on the server response. For this I am using, Zend_Soap_Client passing the WDSL and options in the constructor, I can do getFunctions, but when try to access the first Soap method I get
[Sender] looks like we got no XML document
After looking around and checking the answer I get from the server with soapUI I see that the answer is missing the XML declaration:
<?xml version="1.0" encoding="XXXXXXX"?>
So, is there aby way to make the Zend_Soap_Client omit the XML validation based in the XML declaration? Assuming that the lacking of the declaration is my problem.
Here is the code I use for this:
private $_connection_settings = array('login' => self::API_user, 'pwd' => self::API_password, 'key'=> self::API_Key);
private static $CONNEXION_PARAMS = array(
'soap_version' => SOAP_1_1,
'encoding' => 'UTF-8'
);
...
//somewhere in my code:
$client = new Zend_Soap_Client('http://server_URL?wsdl', self::$CONNEXION_PARAMS);
$response = $client->fistSoapMethod($this->_connection_settings);
And response is not assigned.
Thanks!
No other warnings/errors from your code besides the SOAP Fault?
Not sure it is the WSDL. Can always try validating the WSDL using an online tool.
Have you used getLastResponse() and getLastRequest() methods? Sounds like you might be sending some garbage at the start of your request. Another thing I always do when testing is turn off WSDL caching.
ini_set("soap.wsdl_cache_enabled", 0);
The question:
Is there a way to view the XML that would be created with a PHP SoapClient function call BEFORE you actually send the request?
background:
I am new to WSDL communication, and I have a client who wants me to develop in PHP, a way to communicate with a WSDL service written in ASP.NET. I have gotten pretty far, but am running into an issue when it comes to passing a complex type. I have tried a couple of different things so far.
1) Setting up a single array such as $params->Person->name $params->Person->address
2) Setting up a single array $Person = array('name'=>"joe",'address' = "123");
then passing into the call as a param "Person" => $Person;
and a few others. But every time I get the error
SoapException: Server was unable to
process request ---> System.Exception:
Person is Required. at service name.
In order to further the troubleshooting, I would like to see the XML document that is being sent to see if it is creating a complex type in the way I am expecting it to.
I am creating the service using $client = new SoapClient('wsdldoc.asmx?WSDL'); calling it with $client->CreateUser($params); and then trying to see it using the function $client->__getLastRequest(); but it never makes it to the __getLastRequest because it hits a fatal error when calling CreateUser($params).
The question again:
Is there any way to view the XML created by the CreateUser($params) call WITHOUT actually sending it and causing a fatal error
Upfront remark: In order to use the __getLastRequest() method successfully, you have to set the 'trace' option to true on client construction:
$client = new SoapClient('wsdldoc.asmx?WSDL', array('trace' => TRUE));
This way, your request will still be sent (and therefore still fail), but you can inspect the sent xml afterwards by calling $client->__getLastRequest().
Main answer:
To get access to the generated XML before/without sending the request, you'd need to subclass the SoapClient in order to override the __doRequest() method:
class SoapClientDebug extends SoapClient
{
public function __doRequest($request, $location, $action, $version, $one_way = 0) {
// Add code to inspect/dissect/debug/adjust the XML given in $request here
// Uncomment the following line, if you actually want to do the request
// return parent::__doRequest($request, $location, $action, $version, $one_way);
}
}
You'd then use this extended class instead of the original SoapClient while debugging your problem.
I found this thread while working on the same problem, and was bummed because I was using classes that already extended the SoapClient() class and didn't want to screw around with it too much.
However if you add the "exceptions"=>0 tag when you initiate the class, it won't throw a Fatal Error (though it will print an exception):
SoapClient($soapURL, array("trace" => 1, "exceptions" => 0));
Doing that allowed me to run __getLastRequest() and analyze the XML I was sending.
I don't believe there is a way that you'll be able to see any XML that's being created... mainly because the function is failing on it's attempt to create/pass it.
Not sure if you tried already, but if you're having trouble trying to decide what exactly you need to pass into the function you could use:
$client->__getTypes();
http://us3.php.net/manual/en/soapclient.gettypes.php
Hope this helps!
The built-in PHP extension for SOAP doesn't validate everything in the incoming SOAP request against the XML Schema in the WSDL. It does check for the existence of basic entities, but when you have something complicated like simpleType restrictions the extension pretty much ignores their existence.
What is the best way to validate the SOAP request against XML Schema contained in the WSDL?
Besides the native PHP5 SOAP libs, I can also tell you that neither the PEAR nor Zend SOAP libs will do schema validation of messages at present. (I don't know of any PHP SOAP implementation that does, unfortunately.)
What I would do is load the XML message into a DOMDocument object and use DOMDocument's methods to validate against the schema.
Been digging around on this matter a view hours.
Neither the native PHP SoapServer nore the NuSOAP Library does any Validation.
PHP SoapServer simply makes a type cast.
For Example if you define
<xsd:element name="SomeParameter" type="xsd:boolean" />
and submit
<get:SomeParameter>dfgdfg</get:SomeParameter>
you'll get the php Type boolean (true)
NuSOAP simply casts everthing to string although it recognizes simple types:
from the nuSOAP debug log:
nusoap_xmlschema: processing typed element SomeParameter of type http://www.w3.org/2001/XMLSchema:boolean
So the best way is joelhardi solution to validate yourself or use some xml Parser like XERCES
Typically one doesn't validate against the WSDL. If the WSDL is designed properly there should be an underlying xml schema (XSD) to validate the body of the request against. Your XML parser should be able to do this.
The rest is up to how you implement the web service and which SOAP engine you are using. I am not directly familiar with the PHP engine. For WSDL/interface level "validation" I usually do something like this:
Does the body of the request match a known request type and is it valid (by XSD)?
Does the message make sense in this context and can i map it to an operation/handler?
If so, start processing it
Otherwise: error
Using native SoapServer PHP is a little bit tricky but is possible too:
function validate(string $xmlEnvelope, string $wsdl) : ?array{
libxml_use_internal_errors(true);
//extracting schema from WSDL
$xml = new DOMDocument();
$wsdl_string = file_get_contents($wsdl);
//extracting namespaces from WSDL
$outer = new SimpleXMLElement($wsdl_string);
$wsdl_namespaces = $outer->getDocNamespaces();
//extracting the schema tag inside WSDL
$xml->loadXML($wsdl_string);
$xpath = new DOMXPath($xml);
$xpath->registerNamespace('xsd', 'http://www.w3.org/2001/XMLSchema');
$schemaNode = $xpath->evaluate('//xsd:schema');
$schemaXML = "";
foreach ($schemaNode as $node) {
//add namespaces from WSDL to schema
foreach($wsdl_namespaces as $prefix => $ns){
$node->setAttribute("xmlns:$prefix", $ns);
}
$schemaXML .= simplexml_import_dom($node)
->asXML();
}
//capturing de XML envelope
$xml = new DOMDocument();
$xml->loadXML($xmlEnvelope);
//extracting namespaces from soap Envelope
$outer = new SimpleXMLElement($xmlEnvelope);
$envelope_namespaces = $outer->getDocNamespaces();
$xpath = new DOMXPath($xml);
$xpath->registerNamespace('soapEnv', 'http://schemas.xmlsoap.org/soap/envelope/');
$envelopeBody = $xpath->evaluate('//soapEnv:Body/*[1]');
$envelopeBodyXML = "";
foreach ($envelopeBody as $node) {
//add namespaces from envelope to the body content
foreach($envelope_namespaces as $prefix => $ns){
$node->setAttribute("xmlns:$prefix", $ns);
}
$envelopeBodyXML .= simplexml_import_dom($node)
->asXML();
}
$doc = new DOMDocument();
$doc->loadXML($envelopeBodyXML); // load xml
$is_valid_xml = $doc->schemaValidateSource($schemaXML); // path to xsd file
return libxml_get_errors();
}
and inside your SoapServer function implementation:
function myFunction($param) {
$xmlEnvelope = file_get_contents("php://input");
$errors = validate($xmlEnvelope, $wsdl);
}
I was not able to find any simple way to perform the validation and in the end had validation code in the business logic.
Some time ago I've create a proof of concept web service with PHP using NuSOAP. I don't know if it validates the input, but I would assume it does.