Calling WCF with PHP -- variable structures - php

I have a WCF Service written in .Net 4.0 that accepts two parameters. One is a complex type consisting of User, MerchantName, and Password, the second variable is an int. The service returns a third complex type.
It's structure looks like the following:
//*C# Code *
public sub AccountData Log(Login LoginData, int AccountID)
{
//do stuff here
}
Using SoapClient and removing the int AccountID from the C# service, I can pass the complex data in and parse through the complex data output succesfully. Adding the AccountID parameter, breaks the soap call. I can't seem to compound the variables into one array in a fashion that WCF will accept.
The question is how to form the array to pass in the call?
I have tried the following:
//****Attempt one *******
$login = array('MerchantName' => 'merchantA',
'User' => 'userA',
'password' => 'passwordA');
$account = '68115'; //(also tried $account = 68115; and $account = (int)68115;)
$params = array('LoginData' => $login, 'AccountID' => $account);
$send = (object)$params; //Have tried sending as an object and not.
$client = new SoapClient($wsdl);
$client->soap_defencoding = 'UTF-8';
$result = $client->__soapCall('Log', array($send);
var_dump($send);
echo("<pre>");
var_dump($result);
The latest attempt was to class the variables but I got stuck when tring to form into the $client call.
class LogVar
{
public $MerchantName;
public $User;
public $Password;
}
class AccountID
{
public $AccountID;
}
$classLogin = new LogVar();
$classLogin->MerchantName = 'merchantA';
$classLogin->User = 'userA';
$classLogin->Password = 'passwordA';
$classAccount = new AccountID();
$classAccount->AccountID = '68115';
//How to get to $client->__soapCall('Log', ???????);
P.S. I'm a .Net coder, please be kind with the PHP explanations... Also NuSoap didn't seem much better, however it may have undiscovered ways of dealing with complex types.

This worked for me with standard SoapClient:
$client = new SoapClient($wsdl, array('trace' => true));
$data = $client->Log(array('AccountID' => 23, 'LoginData' => array('User' => '123', 'Password' => '123', 'MerchantName' => '123')));
// echo $client->__getLastRequest();
var_dump($data);
You can display the last request XML and compare it with what a WCF client is generating. This is how I figured it out: I generated a WCF client, inspected the XML message generated by it and compared to $client->__getLastRequest.
Note: You can call the method by its name on a SoapClient rather than use $client->__soapCall('operationName')

Related

Build correct SOAP Request Header (PHP)

I have to do requets to a SOAP API with PHP and I need the following SOAP-Header structure:
<soapenv:Header>
<ver:authentication>
<pw>xxx</pw>
<user>xxx</user>
</ver:authentication>
</soapenv:Header>
How can I build this header?
I tried
$auth = [
"ver:authentication" => [
"pw" => $this->pw,
"user" => $this->user
]
];
$options = [];
$options["trace"] = TRUE;
$options["cache_wsdl"] = WSDL_CACHE_NONE;
$options["compression"] = SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP;
$client = new SoapClient("www.my-url.com/wsdl", $options);
$header = new SoapHeader("www.my-url.com", "authentication", $auth, false);
$client->__setSoapHeaders($header);
but it does not work. The respons is "failure" which I get, when the header structure is incorrect...
please help
the solution could be object driven. In the following code an example is given. Please keep in mind, that the following code is not testet.
class Authentication
{
protected $user;
protected $pw;
public function getUser() : ?string
{
return $this->user;
}
public function setUser(string $user) : Authentication
{
$this->user = $user;
return $this;
}
public function getPw() : string
{
return $this->pw;
}
public function setPw(string $pw) : Authentication
{
$this->pw = $pw;
return $this;
}
}
The above shown class is a simple entity, which contains two properties $user fpr the username and $pw for the password. Further it contains the getter and setter functions for retrieving or setting the values for the two properties.
For the next step just fill the class with data and store it in a SoapVar object.
$authentication = (new Authentication())
->setUser('Username')
->setPw('YourEncodedPassword');
$soapEncodedObject = new \SoapVar(
$authentication,
SOAP_ENC_OBJECT,
null,
null,
'authentication',
'http://www.example.com/namespace'
);
As you can see above, your authentication class will be stored as soap var object. It is encoded as soap object. The only thing you have to do is setting the namespace for this object. In your given example it is ver:. With this namespace prefix somewhere in your wsdl file a namespace is noted. You have to find out this namespace url and just replace the example url http://www.example.com/namespace with the right url noted in your wsdl.
The next step is setting this as soap header. That 's quite simple.
try {
$client = new SoapClient('http://www.example.com/?wsdl', [
'trace' => true,
'exception' => true,
'cache_wsdl' => WSDL_CACHE_NONE,
'compression' => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP,
]);
// set the soap header
$header = new SoapHeader('http://www.example.com/namespace', 'authentication', $authentication, false);
$client->setSoapHeaders($header);
// send the request
$result = $client->someWsdlFunction($params);
} catch (SoapFault $e) {
echo "<pre>";
var_dump($e);
echo "</pre>";
if ($client) {
echo "<pre>";
var_dump($client->__getLastRequest());
echo "</pre>";
echo "<pre>";
var_dump($client->__getLastResponse());
echo "</pre>";
}
}
As you can see it 's a bit different from your given example. Instead of an array it 's the soap encoded authentication object, that is given to the soap header class. For failure purposes there is a try/catch block around your soap client. In that case you can identify the error and if the client was initiated correctly, you can also see the last request and last response in xml.
I hope, that I helped you. ;)
I would strongly advise you 2 things:
Use a WSDL to PHP generator in order to properly construct your request. In addition, it will ease you the response handling. Everything is then using the OOP which is much better. Take a look to the PackageGenerator project.
Use the WsSecurity project in order to easily add your dedicated SoapHeader without wondering how to construct it neither.

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.

Citrix GoToWebinar API Response with teodortalov/citrix

Thank you for taking the time to review my question.
I have been trying to implement the Citrix Go2Webinar Api and found this nice framework: https://github.com/teodortalov/citrix
I have successfully registered users, but I can't figure out how to get the response from the API.
public function user_registration($user, $webinar_id)
{
$client = new \Citrix\Authentication\Direct([$this->api_key]);
$client->auth($this->username, $this->password);
$webinar = new \Citrix\GoToWebinar($client);
$registration = array('firstName' => $user['first_name'], 'lastName' => $user['last_name'], 'email' => $user['email']);
$registrant = $webinar->register($webinar_id, $registration);
return $registrant;
}
The response I get looks like a bunch of protected variables. My question shouldn't I be able to call a method to get the formatted response which looks like what the Citrix API expects to return:
{
"registrantKey": 0,
"joinUrl": "string"
}
those methods are in consumer.php
$registrant = $webinar->register($webinar_id, $registration);
$myJoinUrl = $registration->getJoinUrl();
$myRegistrantKey = $registration->getId();

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;
}

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