I am trying to start with OAuth 1.0 in PHP and I faced weird problem. I created pseudo-Consumer which generates signature according to specification and sends it with used parameters via POST to Provider. Consumer uses:
$oauth_consumer_key = '123';
$oauth_consumer_secret = '456';
$oauth_signature_method = 'HMAC-SHA1';
$oauth_timestamp = time();
$oauth_nonce = uniqid();
$oauth_version = '1.0';
$oauth_callback = 'http://localhost/oauth/callback';
$oauth = new OAuth($oauth_consumer_key, $oauth_consumer_secret);
$oauth->enableDebug();
$oauth_signature = $oauth->generateSignature('POST', $oauth_callback, array($oauth_consumer_key, $oauth_signature_method, $oauth_timestamp, $oauth_nonce, $oauth_version));
On Providers side everything seems to work as intended. All values are received:
object(OAuthProvider)[1]
public 'consumer_key' => string '123' (length=3)
public 'consumer_secret' => string '456' (length=3)
public 'nonce' => string '5390610001c90' (length=13)
public 'token' => null
public 'token_secret' => null
public 'timestamp' => string '1401970944' (length=10)
public 'version' => string '1.0' (length=3)
public 'signature_method' => string 'HMAC-SHA1' (length=9)
public 'callback' => string 'http://localhost/oauth/callback' (length=31)
public 'request_token_endpoint' => boolean true
public 'signature' => string '8lNbnGTOen4TEOHS9KcpgCiBl+M=' (length=28)
But this is the end of honeymoon - attempt to verify signature causes error: signature_invalid. This is what I used on Providers side:
$provider = new OAuthProvider();
$provider->isRequestTokenEndpoint(true);
$provider->consumerHandler('lookupConsumer');
$provider->timestampNonceHandler('timestampNonceChecker');
try
{
$request_verified = $provider->checkOAuthRequest();
}
catch(OAuthException $e)
{
echo $provider->reportProblem($e);
}
and what I receive as an problem report:
oauth_problem=signature_invalid&debug_sbs=POST&http%3A%2F%2Flocalhost%2Foauth%2Fcustom_auth%2Frequest_token.php&oauth_callback%3Dhttp%253A%252F%252Flocalhost%252Foauth%252Fcallback%26oauth_consumer_key%3D123%26oauth_nonce%3D5390610001c90%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1401970944%26oauth_version%3D1.0
As an addition what baffles me is that when I use generateSignature for the same constant parameters (for debugging I set timestamp and nonce to constant values) it gives me every time different value like if there still was some random element I am not aware of. As a validation sample - hash_hmac does not have such issue.
Am I missing something or is there a problem with official PHP OAuth implementation (http://pecl.php.net/package/oauth)?
I have been scratching my head with this exact question for almost a week now because of lack of documentation but this is what solved everything for me.
It seems the OAuth class does its own request signing. I had done the exact same steps as you to no avail but once I removed all the parameters and just called fetch/getRequestToken on my url it all worked.
My code that works
$consumer_key = 'key';
$consumer_secret = 'secret';
$request_token_url = 'http://someurl.com/oauth/request-token';
$oauth = new OAuth($consumer_key, $consumer_secret);
$oauth->enableDebug(); //helpful debug
try {
$oauth->getRequestToken($request_token_url);
} catch (OAuthException $e) {
echo OAuthProvider::reportProblem($e); //easier to debug oauth exceptions
}
//this should hold the `request_token` and `request_token_secret` parameters for you to call getAccessToken
$response = json_decode($oauth->getLastResponse());
I have my own provider set up at http://someurl.com/oauth/request-token that looks like:
$provider = new OAuthProvider();
$provider->consumerHandler(array($this,'consumerHandler'));
$provider->timestampNonceHandler(array($this,'timestampNonceHandler'));
$provider->tokenHandler(array($this,'tokenHandler'));
$provider->setRequestTokenPath('/oauth/request-token');
try {
$request_verified = $provider->checkOAuthRequest();
} catch(OAuthException $e) {
echo $provider->reportProblem($e);
}
//provider now holds all the required timestamp, nonce, and signature
I hope this helps, even though it's a year after
Related
I am trying to link up with Walmart.io API to get some data from their resources. But I am stuck up in the first phase.
According to Walmart.io Quick Start Doc (https://walmart.io/docs/affiliate/quick-start-guide) I am supposed to follow following steps:
Create an account with Walmart.io
Create an application for Web Application
Generate a certificate ( According to their guide there should be some feature to autogenerate the certificate, but I didn't find it)
Upload public key to the application
We will get consumer id and key version using which along with private key, we can make a request. We need to add additional headers that includes Signature and Timestamp too.
So, I did everything, but it still isn't working.
I am using Open SSL to generate private and public key as suggested by them: https://walmart.io/key-tutorial
I tried avoiding -des3 so that it doesn't ask me for passphrase too, but it didn't work either.
Here is the script I tried with
curl --location --request GET 'https://developer.api.walmart.com/api-proxy/service/affil/product/v2/taxonomy' \
--header 'WM_SEC.KEY_VERSION: 2' \
--header 'WM_CONSUMER.ID: <Consumer_ID>' \
--header 'WM_CONSUMER.INTIMESTAMP: 1594389945813' \
--header 'WM_SEC.AUTH_SIGNATURE: W5PEHIew3LsnATk0zxJddeo416YEpMIjvk1b7lW9VMIZFx55erc/5df/FK9UtS5i48q057oASo0AX3SDd2hx+QSeyiX3FtLAgAgiZnGqQ6nJndySWgL5ih/GaUTXIC6dd048GFEZlC6axXdGoTWNzX9P0n/2DwLF9EtvMjdvjB1kum0z2xKz/lQGlvnjVkGK9sZdSUa5rfgxKSPi7ix+LRIJWYwt6mTKUlGz2vP1YjGcZ7gVwAs9o8iFC//0rHUWFwaEGrT0aZJtS7fvSFtKj5NRfemX4fwRO4cgBRxPWy9MRooQwXPmKxRP75PxHKTerv8X6HvRo0GdGut+2Krqxg==' \
And the response I get is
{
"details": {
"Description": "Could not authenticate in-request, auth signature : Signature verification failed: affil-product, version: 2.0.0, env: prod",
"wm_svc.version": "2.0.0",
"wm_svc.name": "affil-product",
"wm_svc.env": "prod"
}
}
Hope someone gives me some insight into this problem.
Thanks in advance
I've had this issue before, it looks like the format of the data you are trying to sign is incorrect.
In node, the content of the template string should look like this: ${consumerId}\n${timeStamp}\n${keyVersion}\n
Turns out it was issue with generated Signature (That explains why it worked after I changed the script.
Thus here is the script that worked fine:
<?php
use GuzzleHttp\Psr7;
use GuzzleHttp\Exception\RequestException;
class Walmart{
private $host;
private $consumer_id;
private $private_key_file;
private $headers;
private $sec_key_version;
private $client;
private $options;
public function __construct($config){
$this->host = $config['host'];
$this->consumer_id = $config['consumer_id'];
$this->private_key_file = $config['private_key_file'];
$this->sec_key_version = $config['sec_key_version'];
$this->options = array();
$this->client = new GuzzleHttp\Client();
}
public function lookup_product($publisher_id='', $ids='', $upc='', $format='json'){
$this->load_options();
$url_params = array(
'format' => $format,
);
if($publisher_id){
$url_params['publisher_id'] = $publisher_id;
}
if($ids){
$url_params['ids'] = $ids;
}
if($upc){
$url_params['upc'] = $upc;
}
$query = http_build_query($url_params);
$url = $this->host . '/product/v2/items?'.$query;
try {
$res = $this->client->request('GET', $url, $this->options);
$body = $res->getBody();
if($res->getStatusCode() == 200){
return $this->response(false, json_decode($body, true));
}else{
return $this->response(array(
'title' => 'Unable to get products',
'stack' => $body,
));
}
} catch (RequestException $e) {
$err = Psr7\str($e->getRequest());
if ($e->hasResponse()) {
$err .= Psr7\str($e->getResponse());
}
return $this->response(array(
'title' => 'Unable to get products',
'stack' => $err,
));
}
}
private function load_options(){
$timestamp = time()*1000;
$this->options = array(
'debug' => (defined("DEBUG") && DEBUG) ? true: false,
'headers' => array(
'WM_SEC.KEY_VERSION' => $this->sec_key_version,
'WM_CONSUMER.ID' => $this->consumer_id,
'WM_CONSUMER.INTIMESTAMP' => $timestamp,
'WM_SEC.AUTH_SIGNATURE' => $this->get_signature($timestamp),
)
);
}
private function get_signature($timestamp){
$message = $this->consumer_id."\n".$timestamp."\n".$this->sec_key_version."\n";
$pkeyid = openssl_pkey_get_private("file://".$this->private_key_file);
openssl_sign($message, $signature, $pkeyid, OPENSSL_ALGO_SHA256);
$signature = base64_encode($signature);
openssl_free_key($pkeyid);
return $signature;
}
private function response($err, $data=false){
return array(
'error' => $err,
'data' => $data,
);
}
}
Note: It uses guzzlehttp/guzzle library for HTTP Request
Here is a full example based on Abiral's post above:
<?php
/**
* Sample script to sign and send a request to the Walmart Affiliate Marketing API.
*
* https://walmart.io/docs/affiliate/introduction
*
* Usage:
* 1. Fill out the required variables at the top of this script.
* 2. Install dependencies via composer install.
* 3. Run via php index.php or by opening this script in a browser.
*
* Acknowledgements:
* Abiral Neupane at https://stackoverflow.com/a/62847241/1120652
* #gorenstein at https://gitter.im/IO-support/community?at=5f2e5d2051bb7d3380d9b58b
*/
include './vendor/autoload.php';
use \GuzzleHttp\Client;
/**
* Create an account at Walmart.io. Then create an application. Then follow the
* steps at https://walmart.io/key-tutorial to create a set of keys. Upload
* the public key (its contents start with BEGIN PUBLIC KEY) into the
* production environment of the application that you created.
*/
$consumer_id = 'Paste here the consumer id that you will see in your application details after pasting the public key';
$key = 'Paste here the private key. Full, including BEGIN and END PRIVATE KEY lines.';
$version = '1';
$timestamp = round(microtime(true) * 1000);
$message = $consumer_id . "\n" . $timestamp . "\n" . $version . "\n";
$pkeyid = openssl_pkey_get_private($key);
openssl_sign($message, $signature, $pkeyid, OPENSSL_ALGO_SHA256);
$signature = base64_encode($signature);
openssl_free_key($pkeyid);
$api = 'https://developer.api.walmart.com';
$product_resource = 'api-proxy/service/affil/product/v2/items/316226539';
$client = new Client(['base_uri' => $api]);
$response = $client->get($product_resource, [
'headers' => [
'WM_SEC.KEY_VERSION' => $version,
'WM_CONSUMER.ID' => $consumer_id,
'WM_CONSUMER.INTIMESTAMP' => $timestamp,
'WM_SEC.AUTH_SIGNATURE' => $signature,
]
]);
print_r(json_decode($response->getBody()->__toString()));
I published the above at https://github.com/juampynr/walmart-api-v2-php
Context / What I want :
I'm facing an issue while calling a Webservice with SOAP. Here's an image the relevant part of the WS I want to call :
(I voluntarily hide the namespace part, not relevant here)
I want to send data through 'Demande_de_mot_de_passe' function and catch result from this request.
In the code below, this request is correct (the name of the function is good), I guess the problem is the formatting of the data I want to send. The call of the function is made with this part :
$client->Demande_de_mot_de_passe($soapVar);
What I've tried :
Here's the relevant part of the code I've tried ( I voluntarily change values of data but nothing else. There is no typo error with the brackets, it close the function and the class I didn't put here to keep the relevant part) :
$client = new \SoapClient('URL_OF_THE_WS?WSDL', array(
'trace' => 1,
'encoding' => 'UTF-8',
'soap_version' => SOAP_1_1,
'classmap' => array('Demande_de_mot_de_passe_Input' => 'Demande_de_mot_de_passe_Input')
));
$donnesUtilisateur = new Demande_de_mot_de_passe_Input;
$donnesUtilisateur->Code_societe = '000';
$donnesUtilisateur->Ident_type = 'A';
$donnesUtilisateur->Ident_code = 'xxxxxx';
$donnesUtilisateur->Dat_demande = '00000000';
$donnesUtilisateur->Adr_mail = 'xxxxxx';
$donnesUtilisateur->Adr_cpos = 'xxxxxx';
$donnesUtilisateur->Nom = 'xxxxxx';
$donnesUtilisateur->Prenom = 'xxxxxx';
$donnesUtilisateur->Dat_naiss = '00000000';
$namespace = 'URL_OF_NAMESPACE';
$soapVar = new \SoapVar($donnesUtilisateur, SOAP_ENC_OBJECT,'Demande_de_mot_de_passe_Input', $namespace);
$result = $client->Demande_de_mot_de_passe($soapVar);
print_r($result);
}
}
class Demande_de_mot_de_passe_Input {
public $Code_societe;
public $Ident_type;
public $Ident_code;
public $Dat_demande;
public $Adr_cpos;
public $Adr_mail;
public $Nom;
public $Prenom;
public $Dat_naiss;
}
I've also tried with passing array of casting an object with the array like this (without success) :
$donnesUtilisateur = [
'Code_societe' => '000',
'Ident_type' => 'A',
'Ident_code' => 'xxxxxx',
'Dat_demande' => '00000000',
'Adr_cpos' => 'xxxxxx',
'Adr_mail' => 'xxxxxx',
'Nom' => 'xxxxxx',
'Prenom' => 'xxxxxx',
'Dat_naiss' => '00000000',
];
and :
$donnesUtilisateur = (object) [
'Code_societe' => '000',
'Ident_type' => 'A',
'Ident_code' => 'xxxxxx',
'Dat_demande' => '00000000',
'Adr_cpos' => 'xxxxxx',
'Adr_mail' => 'xxxxxx',
'Nom' => 'xxxxxx',
'Prenom' => 'xxxxxx',
'Dat_naiss' => '00000000',
];
Error I get :
SoapFault: Did not receive a 'Demande_de_mot_de_passe_Input' object. in SoapClient->__call()
If I unterstand clearly, the formatting of data sent is not correct but when I try other way to send it, it still reporting the same error.
Docs I've read about without success :
http://www.fvue.nl/wiki/Php:_Soap:_How_to_add_attribute_to_SoapVar
http://grokbase.com/t/php/php-soap/066jkmcz2h/passing-objects-to-soap-server-complextype-classmap
EDIT
Here's a capture of the WS 'Demande_de_mot_de_passe' function call in SoapUI :
(Sorry for the long post, I hope it is clear enough, don't forget to ask about precisions if needed, thanks in advance for your help :) )
At your WSDL's type, there's a sequence named Demande_de_mot_de_passe which use a element named Demande_de_mot_de_passeRequest and not Demande_de_mot_de_passe_Input.
Your print from SoapUI describe the message request, but if it's document style, Demande_de_mot_de_passe is a type. On the other hand if it's RPC is the method name.
Starting if it's RPC you can do as showed below. You should use as native object as you can (SOAP will work better with they). A stdObject will be good enough:
$request = new stdClass();
$demande_de_mot_de_passeRequest->Code_societe = '000';
$demande_de_mot_de_passeRequest->Ident_type = 'A';
$demande_de_mot_de_passeRequest->Ident_code = 'xxxxxx';
$demande_de_mot_de_passeRequest->Dat_demande = '00000000';
$demande_de_mot_de_passeRequest->Adr_mail = 'xxxxxx';
$demande_de_mot_de_passeRequest->Adr_cpos = 'xxxxxx';
$demande_de_mot_de_passeRequest->Nom = 'xxxxxx';
$demande_de_mot_de_passeRequest->Prenom = 'xxxxxx';
$demande_de_mot_de_passeRequest->Dat_naiss = '00000000';
$request->Demande_de_mot_de_passeRequest = $demande_de_mot_de_passeRequest;
$response = $client->Demande_de_mot_de_passe($request);
If your SOAP binding is document, you just have to add a new upper level named Demande_de_mot_de_passe
/** The variable $demande_de_mot_de_passeRequest is created as above **/
$demande_de_mot_de_passe = new stdClass();
$demande_de_mot_de_passe->Demande_de_mot_de_passeRequest = $demande_de_mot_de_passeRequest;
$request->Demande_de_mot_de_passe = $demande_de_mot_de_passe;
$response = $client->Demande_de_mot_de_passe($request);
Your WSDL doesn't need a list/collections (it's not your case), so you don't need to create/parse variables with SoapVar. There's others examples that you can read about (one is mine, but it's in portuguese) and other is about the BOGUS node:
http://forum.imasters.com.br/topic/535213-enviar-xml-via-soap/?p=2137411
http://www.fischco.org/blog/2011/3/26/php-soapserver-objects-arrays-and-encoding.html
I have been trying to download a file in Guzzle and it acts wired, Then I noticed that the request URL has gone haywire. I don't understand how to use the setEncodingType(false); function.
This is what I have right now.
public class Foo{
private $client;
private $loginUrl = 'https://<site>/login';
private $parseUrl = 'https://<site>/download';
public function __construct()
{
require_once APPPATH . 'third_party/guzzle/autoloader.php';
$this->client = new GuzzleHttp\Client(['cookies' => true, 'allow_redirects' => [
'max' => 10, // allow at most 10 redirects.
'strict' => true, // use "strict" RFC compliant redirects.
'referer' => true, // add a Referer header
'protocols' => ['https'], // only allow https URLs
'track_redirects' => true
]]);
}
public function download(){
$q_params = array('param_a'=> 'a', 'param_b'=>'b');
$target_file = APPPATH.'files/tmp.log';
$response = $this->client->request('GET', $this->parseUrl,['query'=>$reportVars, 'sink' => $target_file]);
}
}
Can anyone tell me how can I use disable the url encoding in the above code?
Cursory glance through the code of GuzzleHttp\Client::applyOptions indicates that when you utilze the "query" request option the query will be built to PHP_QUERY_RFC3986 as shown below:
if (isset($options['query'])) {
$value = $options['query'];
if (is_array($value)) {
$value = http_build_query($value, null, '&', PHP_QUERY_RFC3986);
}
if (!is_string($value)) {
throw new \InvalidArgumentException('query must be a string or array');
}
$modify['query'] = $value;
unset($options['query']);
}
Guzzle utilizes GuzzleHttp\Psr7\Uri internally. Note how the withoutQueryValue() and withQueryValue() methods will also encode the query string.
I have had a lot of success "hard coding" my query parameters, like the following:
$uri = 'http://somewebsite.com/page.html?param_a=1¶m2=245';
I would like to also note that there is no setEncodingType() within GuzzleHttp\Client.
The Zend_Service_Twitter component is still for Twitters API v1.0 which will be deprecated at 5th March 2013. So I wanted to make my new website with Twitter API interaction v1.1 ready.
Everything works fine with v1.0 but if I change the URL from /1/ to /1.1/ it fails with the HTTP header code 400 and the JSON error message: Bad Authentication data (Code: 215)
To get the request and access token stayed the same and works already without any changes,
but if I want to verify the credentials like this I get the error I described above:
// Take a look for the code here: http://framework.zend.com/manual/1.12/en/zend.oauth.introduction.html
$accessToken = $twitterAuth->getAccessToken($_GET, unserialize($_SESSION['TWITTER_REQUEST_TOKEN']));
// I have a valid access token and now the problematic part
$twitter = new Zend_Service_Twitter(array(
'username' => $accessToken->getParam('screen_name'),
'accessToken' => $accessToken
));
print_r($twitter->account->verifyCredentials());
I changed the code of verifyCredentials in Zend/Service/Twitter.php from that to that:
public function accountVerifyCredentials()
{
$this->_init();
$response = $this->_get('/1/account/verify_credentials.xml');
return new Zend_Rest_Client_Result($response->getBody());
}
// to
public function accountVerifyCredentials()
{
$this->_init();
$response = $this->_get('/1.1/account/verify_credentials.json');
return Zend_Json::decode($response->getBody());
}
Now I added before the return Zend_Json[...] this line:
print_r($this->_localHttpClient->getLastRequest());
// And I get this output of it:
GET /1.1/account/verify_credentials.json HTTP/1.1
Host: api.twitter.com
Connection: close
Accept-encoding: gzip, deflate
User-Agent: Zend_Http_Client
Accept-Charset: ISO-8859-1,utf-8
Authorization: OAuth realm="",oauth_consumer_key="",oauth_nonce="91b6160db351060cdf4c774c78e2d0f2",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1349107209",oauth_version="1.0",oauth_token="hereismytoken",oauth_signature="hereisavalidsignature"
As you could see the oauth_consumer_key (and realm too) is empty. Could that be the error? How could I solve this error (because of the stricter new API version?)? Would it be fine to set somehow the oauth_consumer_key? If yes, how could I manage that?
Edit:
I also found already a bug report on the issue tracker of the Zend Framework:
http://framework.zend.com/issues/browse/ZF-12409 (maybe do an upvote?)
with ZF 1.12.3 the workaround is to pass consumerKey and consumerSecret in oauthOptions option, not directrly in the options.
$options = array(
'username' => /*...*/,
'accessToken' => /*...*/,
'oauthOptions' => array(
'consumerKey' => /*...*/,
'consumerSecret' => /*...*/,
)
);
While you wait to fix this issue in Zend_Twitter_Service component, you can do this workaround:
You need to send customerKey and customerSecret to Zend_Service_Twitter
$twitter = new Zend_Service_Twitter(array(
'consumerKey' => $this->consumer_key,
'consumerSecret' => $this->consumer_secret,
'username' => $user->screenName,
'accessToken' => unserialize($user->token)
));
Today I have the same problem - Zend Framework works with API 1.
I created new class like
class Zend_Service_Twitter11 extends Zend_Service_Twitter
And override functions, which I need.
statusUpdate
statusReplies
etc
$this->_session = new Zend_Session_Namespace('auth_twitter');
$config = Zend_Registry::get('config')->twitter->toArray();
$access_tokenSession = unserialize($this->_session->access_token);
$accessToken = new Zend_Oauth_Token_Access();
$accessToken->setToken($access_tokenSession->oauth_token);
$accessToken->setTokenSecret($access_tokenSession->oauth_token_secret);
$temp = array();
$temp['oauthOptions']['consumerKey'] = $config['consumerKey'];
$temp['oauthOptions']['consumerSecret'] = $config['consumerSecret'];
$temp['accessToken'] = $accessToken;
$temp['username'] = $access_tokenSession->screen_name;
$this->_twitter = new Zend_Service_Twitter($temp, null);
$this->_twitter->account->accountVerifyCredentials()->toValue()
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')