SOAP request with attribute - php

I can not seem to find out how to set an attribute to a SOAP request without using the XSD_ANYXML encoding.
The request parameter should look as follows
<request
xmlns:ns="/some/ns">
...
<ns:parameter attr="some attribute">
value
</ns:parameter>
...
</request>
Of course the following code works, but it's rather ugly (ugly, because it uses string concatenation where it should use the SOAP_Client API and because it does not use the general namespace)
$param = new SoapVar(
'<ns_xxx:parameter xmlns:ns_xxx="/some/ns" attr="some attribute">
value
</ns_xxx:parameter>',
XSD_ANYXML
);
Is there a better way to create a SOAP request parameter with a namespace and an attribute?
I am looking for s.th. like the following (this is just some pseudo code using the SoapVar API):
$param = new SoapVar(
array(
'_' => 'value',
'attr' => 'some attribute'
),
SOME_ENCODING,
null,
null,
null,
'/some/ns'
);

For this, you need to derived the class from SoapClient and Override the method __doRequest():
class ABRSoapClient extends SoapClient {
// return xml request
function __doRequest($request, $location, $action, $version) {
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->preserveWhiteSpace = false;
$xml= $dom->loadXML($request);
// Goto request Node and Set the attribute
$attr_ns = $dom->createAttributeNS('xmlns:ns', '' ); // instead of xmlns:ns use Namespace URL
$attr_ns->value = '/some/ns';
// add atribute in businessReport node
$dom->getElementsByTagName($report_type)->item(0)->appendChild( $attr_ns );
$request = $dom->saveXML();
return parent::__doRequest($request, $location, $action, $version);
}
}
$client = new ABRSoapClient(.....);
$save_result = $client->request($param);
// You can check the form request using function
$client->__getLastRequest();
I hope this will resolve your problem.

SOAP does not support attributes, may be you should use REST instead!
EDIT:
Please check the body style w3c:"4.3 SOAP Body" and remember that
you need to encode your message with "soap-envelope" namespace and describe
your XML types thats why, you can't use attributes to describe your message data.
But if you ask me, it can be made possible! You can use a custom SoapClient parser or something like that and convert your message as you like it.
A example of that may be RSS over SOAP http://www.ibm.com/developerworks/webservices/library/ws-soaprdf.
But, the problem would be that you would miss the descriptive information about your message data/types and other clients could not easy understand your messages!
My best practice for you would be to use elements instead of attributes,
i know you need to fix your XML schema but thats the way it goes or switch to a other technology.

SOAP 1 does support attributes. Here is an example of Perl code using both attributes and values (from a client):
$som = $client->call(
'tran:getContent',
SOAP::Header->name('cred:credentials')->attr({
'username' => $username,
'password' => 'xxx',
'customerID' => 'xxx'}
),
SOAP::Data->name('contentID')->value('9999')
)

Related

Errors while implementing SOAP Web service with PHP

I have to implement a SOAP Web Service using PHP.
I did it by using the SoapServer class and all works fine.
I need to use a specific format for the request: they have to contain a "Header" tag with an "Authentication" tag in which there is a token that I have to use to authenticate the client that performed the request.
I used "file_get_contents('php //input')" to get the entire request that I received and then parsed it to retrieve the token that I needed.
This works fine if I try to simulate a SOAP request by using SoapUI. But, if I try to do the request by using PHP SoapClient and use the function SoapHeader to set the header, on the server side "file_get_contents('php //input')" returns only the fields of the entire request (contained in the XML tags of the XML request) merged together in a string, instead of returning the entire XML in a string format.
I cannot understand why.
The SoapServer class isn 't well documented in the PHP documentation. The SoapServer class does everything that you have in mind completely automatically. You have to use a decorator class. What a decorator is and what it does I 'll explain in the next lines. I 'm trying to give you a push in the right direction.
A while ago I had to implement the WSSE authentication standard. I 'll take some parts from the WSSE standard for this example.
The incoming request had a header that looked like this ...
<soapenv:Header>
<wsse:Security xmlns:wsc="http://schemas.xmlsoap.org/ws/2005/02/sc" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsc:SecurityContextToken>
<wsc:Identifier>identifier</wsc:Identifier>
</wsc:SecurityContextToken>
</wsse:Security>
</soapenv:Header>
The key (identifier) identifies an authorized user to perform a function of the web service. In this sense, we must check that the key is valid before executing any function. For this purpose we need a decorator class, that is executed before the actual function is executed.
class AuthDecorator
{
/**
* Name of the class, which contains the webservice methods
* #var string
*/
protected $class;
/**
* Flag, if the recieved identifier is valid
* #var boolean
*/
protected $isValid = false;
public function getClass() : string
{
return $this->class;
}
public function setClass($class) : AuthDecorator
{
$this->class = $class;
return $this;
}
public function getIsValid() : bool
{
return $this->isValid;
}
public function setIsValid(bool $isValid) : AuthDecorator
{
$this->isValid = $isValid;
return $this;
}
public function __call(string $method, array $arguments)
{
if (!method_exists($this->class, $method)) {
throw new \SoapFault(
'Server',
sprintf(
'The method %s does not exist.',
$method
)
);
}
if (!$this->getIsValid()) {
// return a status object here, wenn identifier is invalid
}
return call_user_func_array(
[ $this->class, $method ],
$arguments
);
}
/**
* Here 's the magic! Method is called automatically with every recieved request
*
* #param object $security Security node form xml request header
*/
public function Security($security) : void
{
// auth against session or database or whatever here
$identifier = $this->getIdentifierFromSomewhereFunc();
if ($security->SecurityContextToken->Identifier == $identfier) {
$this->setIsValid(true);
}
}
}
That 's the decorator class. Looks easy, hm? The decorator contains a class named like the first child of the xml header of the recieved request. This method will be executed automatically every time we recieve a request with the soap server. Beside that the decorator checks, if the called soap server function is available. If not a soap fault is thrown that the soap client on the consumer side recieves. If a method exists is quite easy, too. Every webservice method we put in a class.
class SimpleWebservice
{
public function doSomeCoolStuff($withCoolParams) : \SoapVar
{
// do some fancy stuff here and return a SoapVar object as response
}
}
For illustrative purposes, our web service just has this one function.
But how the hell we bring the decorator to work with the soap server?
Easy, mate. The SoapServer class has some pretty tricky functionality, that is not documented. The class has a method called setObject. This method will do the trick.
$server = new \SoapServer(
$path_to_wsdl_file,
[
'encoding' => 'UTF-8',
'send_errors' => true,
'soap_version' => SOAP_1_2,
]
);
$decorator = new AuthDecorator();
$decorator->setClass(SimpleWebservice::class);
$server->setObject($decorator);
$server->handle();
That 's awesome, right? Just initializing the SoapServer class, add the decorator with the setObject method and run it with the handle method. The soap server recieves all requests and before calling the webservice method the decorator will check, if the identifier is valid. Only if the identifier is valid, the called webservice method will be executed.
How 's the soap client request looking?
On the other side the soap client can look like this ...
$client = new SoapClient(
$path_to_wsdl_file,
[
'cache_wsdl' => WSDL_CACHE_NONE,
'compression' => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP,
'exceptions' => true,
'trace' => true,
]
);
$securityContextToken = new \stdClass();
$securityContextToken->Identifier = 'identifier';
$securityContextToken = new \SoapVar(
$securityContextToken,
SOAP_ENC_OBJ,
null,
null,
'SecurityContextToken',
'http://schemas.xmlsoap.org/ws/2005/02/sc'
);
$security = new stdClass();
$security->SecurityContextToken = $securityContextToken;
$security = new \SoapVar(
$security,
SOAP_ENC_OBJ,
null,
null,
'Security',
'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
);
$header = new \SoapHeader(
'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd',
'Security',
$security
);
$client->__setSoapHeaders($header);
$result = $client->doSomeCoolStuff(new \SoapParam(...));
Conclusion
When working in an object orientated context the SoapServer and SoapClient classes are pretty cool. Because the documentation doesn 't really give much about both classes, you have to test and learn. You can easily create a SOAP webservice when you know how. Without writing any xml as a string.
Before you productively use the code examples seen here, please make sure that they are only examples and not intended for productive use. The shown examples should push you in the right direction. ;)
Questions?

SOAP PHP : how to translate request file to PHP function call

I'm beginning with the SOAP lib of PHP and i can't figure out how to execute my request :
The server has a user friendly API which gives me the request to pass but i can't tell how I am supposed to do so.
Here is the point I currently am :
$soap = new SoapClient("https://www.dmc.sfr-sh.fr/DmcWS/1.5.6/MessagesUnitairesWS?wsdl");
$soap->getSingleCallCra();
and the request i should pass :
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://servicedata.ws.dmc.sfrbt/">
<soapenv:Header>
<ser:authenticate>
<serviceId>********</serviceId>
<servicePassword>******</servicePassword>
<spaceId>*******</spaceId>
<lang>fr_FR</lang>
</ser:authenticate>
</soapenv:Header>
<soapenv:Body>
<ser:getSingleCallCra>
<beginDate>2017-10-17T00:00:00</beginDate>
</ser:getSingleCallCra>
</soapenv:Body>
</soapenv:Envelope>
The SOAP client does work for other function with no parameter but i get a translated java NPE exception when i call this function.
Can anyone tell me how i can pass the parameters and authentification to the function ?
Thanks.
$soap = new SoapClient("https://www.dmc.sfr-sh.fr/DmcWS/1.5.6/MessagesUnitairesWS?wsdl");
To add headers to a soapcall use the __setSoapHeaders method like this:
$soap->__setSoapHeaders(array(
//(namespace, name, data)
new SoapHeader("http://servicedata.ws.dmc.sfrbt/",'authenticate',array(
'serviceId' => '********',
'servicePassword' => '******',
'spaceId' => '*******',
'lang' => 'fr_FR',
))
));
These parameters will go into the soap body. In PHP you can use objects or associative arrays as input as they are both interpreted into xml as key => value pairs.
$soap_body_parameters = array(
'beginDate' => '2017-10-17T00:00:00',
);
$response = $soap->getSingleCallCra($soap_body_parameters);
print_r($response);
The return value of the soapclient class is always an object, so remember to use the arrow notation '$object->property' to get the relevant data out.
You can also create a class like this, that will deal with the headers, data extraction, etc. in the background for each call
class sfr_soap {
function __construct($serviceId, $servicePassword, $spaceId, $lang = 'fr_FR'){
$url = "https://www.dmc.sfr-sh.fr/DmcWS/1.5.6/MessagesUnitairesWS?wsdl";
$this->client = new SoapClient($url);
$soap->__setSoapHeaders(array(
new SoapHeader("http://servicedata.ws.dmc.sfrbt/",'authenticate',array(
'serviceId' => $serviceId,
'servicePassword' => $servicePassword,
'spaceId' => $spaceId,
'lang' => $lang,
))
));
}
public function __call($name, $args = array()){
$response = $this->client->$name($args);
// do something with the response here, like extract the meaningful parts of the data
return $response;
}
}
init like this
$sfr = new sfr_soap($serviceId, $servicePassword, $spaceId);
or like this if you want to specify the language
$sfr = new sfr_soap($serviceId, $servicePassword, $spaceId, $lang);
use like this
$data = $sfr->getSingleCallCra(array(
'beginDate' => '2017-10-17T00:00:00'
));
You can pass arguments to a SOAP function call multiple ways as it is stated in the documentation: SoapClient::__soapCall
An array of the arguments to pass to the function. This can be either an ordered or an associative array. Note that most SOAP servers require parameter names to be provided, in which case this must be an associative array.
So in your case, the call should be:
$soap->getSingleCallCra(array(
'beginDate' => '2017-10-17T00:00:00',
));
I hope, I could be of any help.

PHP SOAP client namespacing

I have a SoapClient instance and I'm trying to make a request (duh!). I am able to pass an array of parameters as key => value in the first level like, securityToken. But I can't send to the second namespace (I think that's what it is) stap. The following is a simplified version of what the inside of my ENV should look like. I know the Envelope should contain a reference to xmlns:stap but I can't work out how to get SoapClient to do that.
<soapenv:Body>
<ns:PlaceOrder>
<ns:securityToken></ns:securityToken>
<ns:orderRequest>
<stap:Headers>
<stap:OrderRequestHeader>
<stap:Lines>
<stap:OrderRequestLine>
<stap:QuantityRequested></stap:QuantityRequested>
<stap:StockCode></stap:StockCode>
</stap:OrderRequestLine>
</stap:Lines>
</stap:OrderRequestHeader>
</stap:Headers>
</ns:orderRequest>
</ns:PlaceOrder>
And here's my _soap function
protected function _soap($request, $parameters = array(), $service = null, $options = array()) {
$client = new SoapClient($service, $options);
$response = $client->{$request}($parameters);
return $response;
}

Run report from JasperServer using PHP SOAP client

I want to run a report from JasperServer using a PHP SOAP client. I found this example online, but I want to attach an XML data source, used for the report data, and I am unsure how it should be correctly attached.
How can I attach my XML data source to the SOAP request, that is acceptable to Jasper Server?
public function requestReport($report, $format, $params) {
$params_xml = "";
foreach ($params as $name => $value) {
$params_xml .= "<parameter name=\"$name\"><![CDATA[$value]]></parameter>\n";
}
$request = "
<request operationName=\"runReport\" locale=\"en\">
<argument name=\"RUN_OUTPUT_FORMAT\">$format</argument>
<resourceDescriptor name=\"\" wsType=\"\"
uriString=\"$report\"
isNew=\"false\">
<label>null</label>
$params_xml
</resourceDescriptor>
</request>
";
$client = new SoapClient(null, array(
'location' => $this->url,
'uri' => 'urn:',
'login' => $this->username,
'password' => $this->password,
'trace' => 1,
'exception'=> 1,
'soap_version' => SOAP_1_1,
'style' => SOAP_RPC,
'use' => SOAP_LITERAL
));
$pdf = null;
try {
$result = $client->__soapCall('runReport', array(
new SoapParam($request,"requestXmlString")
));
$pdf = $this->parseReponseWithReportData(
$client->__getLastResponseHeaders(),
$client->__getLastResponse());
} catch(SoapFault $exception) {
$responseHeaders = $client->__getLastResponseHeaders();
if ($exception->faultstring == "looks like we got no XML document" &&
strpos($responseHeaders, "Content-Type: multipart/related;") !== false) {
$pdf = $this->parseReponseWithReportData($responseHeaders, $client->__getLastResponse());
} else {
throw $exception;
}
}
if ($pdf)
return $pdf;
else
throw new Exception("Jasper did not return PDF data. Instead got: \n$pdf");
}
The full example I found here https://gist.github.com/26205
The goal it to create something like this:
This is more a comment than an answer, but probably helpful. There is a library called WSO2 WSF/PHP:
WSO2 WSF/PHP is intended to fill some of the gaps in the PHP extension. WSO2 WSF/PHP is an open source implementation like the SOAP extension and supports MTOM, WS-Addressing, WS-Security, and WS-RelaiableMessaging. WSO2 WSF/PHP supports a similar API to that of the SOAP extension. There are plans to wrap the API to provide the same API of the SOAP extension; it will be written in C.
I think you're looking for Binary attachment (MTOM).
The following links might be useful as well:
PHP SOAP Messages with AttachmentsPEAR::SOAP related
MIME Attachments using SoapClient ClassMailing List
SOAP Messages with AttachmentsW3C
PHP SOAP ExtensionIntroduction
On their own site they have also some examples about integration to Web Services via php.
Is there any help for this?
SOAP request has no attachment support.
The idea is how you process your request.
The only way I use SOAP requests with attachments is to Base64 Encode the data to be attached, and add it to a Text node.
Add the tag with attribute encoded="true/false". If its a file content, supply the name of the file in the request.
In the server side, if you find the node with an attribute encoded="true", You can take the data from the node, Base64Decode it and do what ever you need.
The idea of Base64 is to avoid many special characters that a SOAP request doesn't support in Request. Some SOAP processors have the option with "encoded" attribute.
You want to include a xml file in your SOAP query or response?
You could encode it base64 like in emails and then require the user on the other end to decode it.
$data = chunk_split(base64_encode($xml_data));
Then just add it in a seperate xml tag in your SOAP query/response.

PHP Soap is hell

If I run this
$HostTransactionInfo = new HostTransactionInfo(); // std Object
$HostTransactionInfo->SecurenetID = $cc->merchant->data[$this->name]['secure_net_id'];
$HostTransactionInfo->SecureKey = $cc->merchant->data[$this->name]['secure_key'];
$HostTransactionInfo->Test = self::TEST;
$securenet = new SoapClient(self::WSDL, array('features' => SOAP_SINGLE_ELEMENT_ARRAYS));
$host_trans_info = new SoapVar($HostTransactionInfo, SOAP_ENC_OBJECT);
var_dump($host_trans_info);
$save = $securenet->Process_Save($host_trans_info);
I receive this on every variation: "Server was unable to process request. ---> Object reference not set to an instance of an object."
My SoapClient::__getTypes() request gives me this:
array(
[2] => struct HostTransactionInfo {
string SecurenetID;
string SecureKey;
string Test;
}
[6] => struct Process_Save {
HostTransactionInfo oTi;
}
)
My SoapClient::__getFunctions() request gives me this:
array (
[2] => Process_SaveResponse Process_Save(Process_Save $parameters)
)
Does anyone have any clue as to what I'm doing wrong?
The error is returned by the securenet webservice. Why not contact their support?
But in any case, the server should return a more informative message than "Object reference not set to an instance of an object". The fact that their code dereferences null pointers when it gets some unexpected input doesn't bode well for something that's supposed to be a "secure" payment system.
Please check the XML request that is sent to the server and the XML response you get back:
// ...
$securenet = new SoapClient(self::WSDL, array(
'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
'trace' => true // that's important for the debugging methods to work
));
// ...
$save = $securenet->Process_Save($host_trans_info);
var_dump($securenet-> __getLastRequestHeaders());
var_dump($securenet-> __getLastRequest());
var_dump($securenet-> __getLastResponseHeaders());
var_dump($securenet-> __getLastResponse());
This will help to get you an overview of what's happening on the wire. If you can rule out any server-related problem, the error will most likely be related to a XML-SOAP-request that is not in the required format.
Hi this might be to late for the original asker, But for anyone who may have the same error ...
this is a trick i learned when i was working with M$.Net or C# (CVS) or any other flavor of M$ Soap servers... M$ changes something in the envelope and that is where things go wrong...
class MSSoapClient extends SoapClient {
function __doRequest($request, $location, $action, $version) {
$namespace = "http://tempuri.org/";
$request = preg_replace('/<ns1:(\w+)/', '<$1 xmlns="'.$namespace.'"', $request, 1);
$request = preg_replace('/<ns1:(\w+)/', '<$1', $request);
$request = str_replace(array('/ns1:', 'xmlns:ns1="'.$namespace.'"'), array('/', ''), $request);
// parent call
return parent::__doRequest($request, $location, $action, $version);
}
}
This will correct the envelope and correct the error in most cases... look at the variable $namespace = "http://tempuri.org/"; make sure this is correct based on the WSDL file
I dont know if this will fix the USER's error but it might help others with similar errors

Categories