I am looking for a way to change the namespace: "ns1" into "ret", tested the below XML using SoapUI with the namespace set to "ret" and the request was successful. I've "googled", and searched the answers from other related questions here in SO, but no luck. So, I am kind of desperate to find the answer...
Here is the XML that is generated to be sent to request:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://retailexpress.com.au/">
<SOAP-ENV:Header>
<ns1:ClientHeader>
<ns1:ClientID>Random-hash-clientID</ns1:ClientID>
<ns1:UserName>Username</ns1:UserName>
<ns1:Password>Password</ns1:Password>
</ns1:ClientHeader>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ns1:CustomerGetBulkDetails>
<ns1:LastUpdated>2000-01-01T00:00:00.000Z</ns1:LastUpdated>
<ns1:OnlyCustomersWithEmails>1</ns1:OnlyCustomersWithEmails>
</ns1:CustomerGetBulkDetails>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Though, it seems a bit odd that we have to request with the same namespace (ret), but that's how it is...
Here is the PHP code used to generate the above:
$rexHost = '<domain of retail express>';
$rexApi = '<URI of retail express API / wsdl path>';
$rexUser = 'Username';
$rexPassword = 'Password';
$rexApiClient = 'Random-hash-clientID';
$rexApiHost = 'http://retailexpress.com.au/';
$client = new SoapClient($rexHost.$rexApi, array('trace' => true));
$auth = new stdClass();
$auth->ClientID = $rexApiClient;
$auth->UserName = $rexUser;
$auth->Password = $rexPassword;
$header = new SoapHeader($rexApiHost, 'ClientHeader', $auth, false);
$client->__setSoapHeaders($header);
$lastUpdate = '2000-01-01T00:00:00.000Z'; //hardcoded for test
$params = array();
$params[] = new SoapVar($lastUpdate, XSD_DATETIME, null, null, 'LastUpdated', $rexApiHost);
$params[] = new SoapVar(1, XSD_INTEGER, null, null, 'OnlyCustomersWithEmails', $rexApiHost);
try {
$users = null;
return $users = $client->CustomerGetBulkDetails( new SoapVar($params, SOAP_ENC_OBJECT));
} catch (Exception $e) {
Log::info($e->getMessage());
Log::info($client->__getLastRequest()); //laravel logger, where I got the generated SOAP XML request
return false;
}
ns1 is not the namespace but an alias for it. http://retailexpress.com.au/ is the namespace. The namespace definition xmlns:ns1="http://retailexpress.com.au/" defines an alias for the current element and its descendants. A namespace has to be unique and stable. Using a definition and aliases allows for complex URI used as an namespace and short, readable aliases for the serialization.
The following three examples are all resolved to and element CustomerGetBulkDetails in the namespace http://retailexpress.com.au/ by an XML parser:
<ns1:CustomerGetBulkDetails xmlns:ns1="http://retailexpress.com.au/"/>
<ret:CustomerGetBulkDetails xmlns:ret="http://retailexpress.com.au/"/>
<CustomerGetBulkDetails xmlns="http://retailexpress.com.au/"/>
In other words, if the XML/SOAP implementation work correctly it does not matter which alias - ns1 or ret - is used for the namespace.
Related
I want to retrieve the value of a parameter from my salesforce instance. For example, I want to recover the trusted IP range:
https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_securitysettings.htm
To do this, use the Metadata API. To access to this API, I use the force.com toolkit for PHP.
However, the examples given only deal with the creation or the update of the parameters:
https://developer.salesforce.com/blogs/developer-relations/2011/11/extending-the-force-com-toolkit-for-php-to-handle-the-metadata-api.html
How to simply get the value of the parameter (for example the trusted IP range)?
The PHP toolkit shipped by Salesforce is quite outdated and should not be used. There are more recent forks/community projects (one,two) that attempt to implement a modern PHP client, perhaps one of these will work for you.
A simple(r) solution is to retrieve the SecuritySettings via a plain SOAP call to the Metadata API. The request payload with API version set to 46.0 should be
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>Security</members>
<name>Settings</name>
</types>
<version>46.0</version>
</Package>
and the response looks like this (only relevant portion is shown, the actual response is much larger)
<?xml version="1.0" encoding="UTF-8"?>
<SecuritySettings xmlns="http://soap.sforce.com/2006/04/metadata">
<networkAccess>
<ipRanges>
<description>...</description>
<end>1.255.255.255</end>
<start>0.0.0.0</start>
</ipRanges>
</networkAccess>
</SecuritySettings>
Translating to PHP:
$wsdl = PUBLIC_PATH . '/wsdl-metadata.xml';
$apiVersion = 46.0;
$singlePackage = true;
$members = 'Security';
$name = 'Settings';
$params = new StdClass();
$params->retrieveRequest = new StdClass();
$params->retrieveRequest->apiVersion = $apiVersion;
$params->retrieveRequest->singlePackage = $singlePackage;
$params->retrieveRequest->unpackaged = new StdClass();
$params->retrieveRequest->unpackaged->version = $apiVersion;
$params->retrieveRequest->unpackaged->type = new stdClass();
$params->retrieveRequest->unpackaged->type->members = $members;
$params->retrieveRequest->unpackaged->type->name = $name;
$option = [
'trace' => TRUE,
];
// Namespaces
$namespace = 'http://soap.sforce.com/2006/04/metadata';
$client = new SoapClient($wsdl, $option);
$header = new SoapHeader($namespace, "SessionHeader", array ('sessionId' => $token)); // NEED: access token
$client->__setSoapHeaders($header);
$client->__setLocation($serverUrl); // NEED: service endpoint URL
$serviceResult = $client->retrieve($params);
You'll need to provide an access token ($token) and a service endpoint ($serverUrl).
For anyone trying to get this to work, identigral's example didn't work for me, had to do the following:
change $client->setEndpoint($serverUrl); to $client->__setLocation($serverUrl);
I was using Oauth to login, so you'll need to construct the $serverUrl from the response:
<instance_url> + '/services/Soap/m/46.0/' + <org id (from id)>
Example:
'https://your-production-or-sandbox-name.my.salesforce.com/services/Soap/m/46.0/your-org-id'
I am trying to get request with this structure:
<SOAP-ENV:Body>
<ns1:getCreditReportTypes>
<reportTypeRequest>
<reportParams xsi:type="ns1:personCreditReportParams">
<personId>4</personId>
<consentConfirmed>true</consentConfirmed>
</reportParams>
</reportTypeRequest>
</ns1:getCreditReportTypes>
</SOAP-ENV:Body>
Here is my php-code:
$obj = new \stdClass();
$obj->personId = 4;
$obj->consentConfirmed = true;
$data = new \SoapVar($obj, SOAP_ENC_OBJECT, "personCreditReportParams", $namespace, "reportParams");
$res = $this->client->getCreditReportTypes(new \SoapParam($data,"reportTypeRequest"));
However, php generates invalid xml:
<SOAP-ENV:Body>
<ns1:getCreditReportTypes xsi:type="ns1:personCreditReportParams">
<consentConfirmed>true</consentConfirmed>
<personId>4</personId>
</ns1:getCreditReportTypes>
</SOAP-ENV:Body>
How can I make a valid XML with object-way?
You should definitively use a WSDL to php generator such as PackageGenerator.
It'll ease you the request construction, the response handling.
For those who'll get the same problem.
My solution is to use nusoap (https://github.com/yaim/nusoap-php7). This library allows you to make complicated requests, including SWA (SOAP with Attachments).
Here is working code for my question:
$person = array("personId"=>$id, "consentConfirmed"=>$confirmed);
$data = array(
"reportParams"=>new soapval("reportParams", "personCreditReportParams", $person, false, $namespace)
);
$result = $client->call("getCreditReportTypes", $data, $namespace);
P.S. I've tried some generators and no one could make correct request, although classes were generated correctly.
I am attempting to create this output in PHP:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:ns0="http://webservices.company.co.uk/AddressMatching" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns2="http://webservices.company.co.uk/ServiceBase/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<ns1:Body>
<ns0:GetAvailableAddresses>
<ns0:request>
<ns2:UserCredentials>
<ns2:AgentID>123</ns2:AgentID>
<ns2:Password>PASSword</ns2:Password>
<ns2:Username>user#company.com</ns2:Username>
</ns2:UserCredentials>
<ns0:Address>
<ns0:PostCode>NG42DJ</ns0:PostCode>
</ns0:Address>
</ns0:request>
</ns0:GetAvailableAddresses>
</ns1:Body>
</SOAP-ENV:Envelope>
This is fairly easy in Python, the class I'm using (suds) simply reads the wsdl file, and uses the namespaces correctly:
from suds.client import Client
client = Client('/the/path/to/AddressMatchingService.wsdl')
creds = { 'AgentID': '123', 'Username': 'user#company.com', 'Password': 'PASSword' }
address = client.factory.create('Address')
address.PostCode = "NG42DJ"
address_request = client.factory.create('AvailableAddressesRequest')
address_request.UserCredentials = creds
address_request.Address = address
request = client.service.GetAvailableAddresses(address_request)
print request
There is no need to reference anything to do with namespaces, it simply works by reading the wsdl file and figuring it out. As you can see in the original XML above, the variables have namespaces and inherit where required, also note that the Body is in the ns1 namespace.
The closest I have got in PHP, is using a WSDL-to-PHP converter which generates a ton of classes based on functions within the file, but it seems to lose all sense of namespacing by doing this. The only way of working with it I can see so far is to modify the generated class files like this:
// some code omitted...
use SoapVar; // added by me
// this is declared in AddressMatchingService namespace
class Credentials
{
public $AgentID = null;
public $Username = null;
public $Password = null;
public function __construct($AgentID, $Username, $Password)
{
//$this->AgentID = $AgentID;
$this->AgentID = new SoapVar($AgentID, null, null, null, null, "http://webservices.company.co.uk/ServiceBase/");
//$this->Username = $Username;
$this->Username = new SoapVar($Username, null, null, null, null, "http://webservices.company.co.uk/ServiceBase/");
//$this->Password = $Password;
$this->Password = new SoapVar($Password, null, null, null, null, "http://webservices.company.co.uk/ServiceBase/");
}
}
$wsdl = base_path() . '/resources/wsdl/AddressMatchingService.wsdl';
$soap = new SoapClient($wsdl, array('trace'=>1));
$creds = new AddressMatchingService\Credentials('123', 'user#company.com','PASSword');
$address = new AddressMatchingService\Address('NG42DJ');
$request = array('request' => $request);
$request = new SoapVar($request, SOAP_ENC_OBJECT);
$response = $soap->__soapCall("GetAvailableAddresses", array('request' => $request));
Which gets me close-ish:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://webservices.company.co.uk/ServiceBase/" xmlns:ns2="http://webservices.company.co.uk/AddressMatching">
<SOAP-ENV:Body>
<ns2:GetAvailableAddresses>
<request>
<Address>
<PostCode>NG42DJ</PostCode>
</Address>
<ns1:UserCredentials>
<ns1:AgentID>123</ns1:AgentID>
<ns1:Password>PASSword</ns1:Password>
<ns1:Username>user#company.co.uk</ns1:Username>
</ns1:UserCredentials>
<UPRN/>
</request>
</ns2:GetAvailableAddresses>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
But if it's so easy in Python, I thought I might be doing something wrong. Specifying the namespace in each class seems a pain, and I still don't know if it's possible to change the namespace of the Body from 'SOAP-ENV' to ns1. I would like to avoid handcrafting XML if I could avoid it, as there's so many functions and variables in the WSDL, I'd ideally like to make it work as efficiently as the Python suds library does!
SOAP-ENV or ns1 are not the namespaces, but aliases for them. The actual namespaces are the values in the xmlns:* attributes. In your first example, they resolve both to the same namespace.
SOAP-ENV:Envelope -> {http://schemas.xmlsoap.org/soap/envelope/}Envelope
SOAP-ENV:Header -> {http://schemas.xmlsoap.org/soap/envelope/}Header
ns1:Body -> {http://schemas.xmlsoap.org/soap/envelope/}Body
The alias does not change the meaning. Having different aliases for the same namespace could be considered bad, because it lowers the readability.
Second, just because the default API only has some calls with lots of specific arguments does not mean that you have to use them directly. Think about using helper methods or loops to avoid repeating yourself:
class Credentials
{
private namespaceUri = "http://webservices.company.co.uk/ServiceBase/";
public $AgentID = null;
public $Username = null;
public $Password = null;
public function __construct($AgentID, $Username, $Password)
{
$this->AgentID = $this->createSoapValue($AgentID);
$this->Username = $this->createSoapValue($Username);
$this->Password = $this->createSoapValue($Password);
}
private function createSoapValue($value) {
return new new SoapVar($value, null, null, null, null, $this->namespaceUri);
}
}
Last check the request element. I think you forgot the namespace for it.
I'm using PHP's SoapClient to consume a SOAP service but am receiving an error that the SOAP service cannot see my parameters.
<tns:GenericSearchResponse xmlns:tns="http://.../1.0">
<tns:Status>
<tns:StatusCode>1</tns:StatusCode>
<tns:StatusMessage>Invalid calling system</tns:StatusMessage>
</tns:Status>
</tns:GenericSearchResponse>
The XML PHP's SoapClient sends for the SOAP call:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://.../1.0">
<SOAP-ENV:Body>
<ns1:GenericSearchRequest>
<UniqueIdentifier>12345678</UniqueIdentifier>
<CallingSystem>WEB</CallingSystem>
</ns1:GenericSearchRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
I used soap-ui initially, that works successfully when consuming the same WSDL. The XML soap-ui sends for the call:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://.../1.0">
<SOAP-ENV:Body>
<ns1:GenericSearchRequest>
<ns1:UniqueIdentifier>12345678</ns1:UniqueIdentifier>
<ns1:CallingSystem>WEB</ns1:CallingSystem>
</ns1:GenericSearchRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The difference being the UniqueIdentifier and CallingSystem parameters are prefixed with ns1 in the soap-ui request.
I've tried using passing SoapVar objects to the SoapClient call but this does not augment the parameter tags and prefix them with ns1.
I know that WEB is a valid CallingSystem value as the XSD specifies it, and it works when using soap-ui.
My current SoapClient code:
try {
$client = new SoapClient($wsdl, array('trace' => 1));
$query = new stdClass;
$query->UniqueIdentifier = $id;
$query->CallingSystem = 'WEB';
$response = $client->GenericUniqueIdentifierSearch($query);
} catch (SoapFault $ex) {
$this->view->error = $ex->getMessage();
...
}
I found this blog post but I was hoping there might be a cleaner implementation.
Update:
Used a solution from this question but is pretty clunky:
$xml = "<ns1:GenericSearchRequest>"
. "<ns1:UniqueIdentifier>$id</ns1:UniqueIdentifier>"
. "<ns1:CallingSystem>WEB</ns1:CallingSystem>"
. "</ns1:GenericSearchRequest>";
$query = new SoapVar($xml, XSD_ANYXML);
$response = $this->client->__SoapCall(
'GenericUniqueIdentifierSearch',
array($query)
);
The reasonable way I found to do this, is to use a combination of SoapVar and SoapParam.
Note, SoapVar has options to specify the namespace of each var.
So your code should be something like:
$wrapper = new StdClass;
$wrapper->UniqueIdentifier = new SoapVar($id, XSD_STRING, "string", "http://www.w3.org/2001/XMLSchema", "UniqueIdentifier", "ns1");
$wrapper->CallingSystem = new SoapVar("WEB", XSD_STRING, "string", "http://www.w3.org/2001/XMLSchema", "CallingSystem", "ns1");
$searchrequest = new SoapParam($wrapper, "GenericSearchRequest");
try{
$response = $this->client->GenericUniqueIdentifierSearch($searchrequest);
}catch(Exception $e){
die("Error calling method: ".$e->getMessage());
}
If you get an issue where the attributes and the method are getting different namespaces, try specifying the namespace for your SoapVar as the url as defined in your envelope (in your example: "http://.../1.0") like:
$wrapper->UniqueIdentifier = new SoapVar($id, XSD_STRING, "string", "http://www.w3.org/2001/XMLSchema", "UniqueIdentifier", "http://.../1.0");
See the Soap constants for a list of all XSD_* constants.
Use a SoapVar to namespace the GenericSearchRequest fields:
$xml = "<ns1:GenericSearchRequest>"
. "<ns1:UniqueIdentifier>$id</ns1:UniqueIdentifier>"
. "<ns1:CallingSystem>WEB</ns1:CallingSystem>"
. "</ns1:GenericSearchRequest>";
$query = new SoapVar($xml, XSD_ANYXML);
$response = $this->client->__SoapCall(
'GenericUniqueIdentifierSearch',
array($query)
);
I'm trying to communicate with the eWay server and had everything working until we ended up needing to switch to a different API. The problem is that SoapClient is creating a different namespace for the header (that includes the authentication) then from the body, which, obviously, doesn't get me any results. Instead, I get eWay's servers saying it must have the authentication information.
Here's my code:
$client = new SoapClient($url.'?WSDL', array('trace'=>TRUE));
// Set our SOAP Headers for authentication
$header_body = array(
'eWAYCustomerID' => $gateway['customer_id'],
'Username' => $gateway['username'],
'Password' => $gateway['password']
);
$header_var = new SoapVar($header_body, SOAP_ENC_OBJECT);
$header = new SOAPHeader('http://www.eway.com.au/gateway/managedpayment', 'eWAYHeader', $header_body);
//$client->__setSoapHeaders($header);
try {
$response = $client->__soapCall($action, $xml, null, $header);
} catch (SoapFault $e)
{
echo 'SOAP Fault: '. $e->getMessage()."<br>\n";
}
As you can see, I've tried it with and without using a SoapVar for the header, all with no luck.
Here's the XML request that is being created:
<soap-env:envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="https://www.eway.com.au/gateway/managedpayment" xmlns:ns2="http://www.eway.com.au/gateway/managedpayment">
<soap-env:header>
<ns2:ewayheader>
<ewaycustomerid>87654321</ewaycustomerid>
<username>test#eway.com.au</username>
<password>test123</password>
</ns2:ewayheader>
</soap-env:header>
<soap-env:body>
<ns1:createcustomer>...</ns1:createcustomer>
This may be an obvious question, but did you try specifying the typename and type namespace in the SoapVar() call?