PHP Soap client with complex types - php

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.

Related

How to retrieve metadata from salesforce with force.com toolkit for PHP?

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'

Changing namespace in PHP SoapClient request?

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.

EWS Push Subscription using PHP

Does anyone know how to respond to EWS (Exchange Web Services) Push Notifications using PHP.
I have initiated the EWS Push Subscription but cannot seem to send the correct SOAP response (in order to keep the subscription alive) when EWS sends my service a SOAP notification.
Taken from this page, I was under the impression that my SOAP response should be as follows:
<s:Envelope xmlns:s= "http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<SendNotificationResult xmlns="http://schemas.microsoft.com/exchange/services/2006/messages">
<SubscriptionStatus>OK</SubscriptionStatus>
</SendNotificationResult>
</s:Body>
</s:Envelope>
However, EWS doesn't seem to be accepting my response as valid.
I have tried the following 2 code snippets with no luck:
Respond using a SOAP string with Content-Type header
header( 'Content-Type: text/xml; charset=utf-8' );
echo '<?xml version="1.0" encoding="utf-8"?>'.
'<s:Envelope xmlns:s= "http://schemas.xmlsoap.org/soap/envelope/">'.
'<s:Body>'.
'<SendNotificationResult xmlns="http://schemas.microsoft.com/exchange/services/2006/messages">'.
'<SubscriptionStatus>OK</SubscriptionStatus>'.
'</SendNotificationResult>'.
'</s:Body>'.
'</s:Envelope>';
OR Respond using a SOAP service
class ewsService {
public function SendNotification( $arg ) {
$result = new EWSType_SendNotificationResultType();
$result->SubscriptionStatus = 'OK';
return $result;
}
}
$server = new SoapServer( null, array(
'uri' => $_SERVER['REQUEST_SCHEME'].'://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'],
));
$server->setObject( new ewsService() );
$server->handle();
It might help to know that the classes I am using in my code come from the PHP-EWS library.
Any help would be greatly appreciated.
I also posted a more specific question here, but have had no responses so thought I would ask whether someone has actually gotten this working using any method.
It would seem that I was close but needed to include the NotificationService.wsdl when instantiating the SoapServer class. The WSDL then allows that SoapServer class to format the response accordingly.
$server = new SoapServer( PHPEWS_PATH.'/wsdl/NotificationService.wsdl', array(
The WSDL was not included in the php-ews library download, however it is included with the Exchange Server installation. If like me you don't have access to the Exchange Server installation you can find the file here. I also had to add the following to the end of the WSDL because I was storing the WSDLs locally and not using autodiscovery:
<wsdl:service name="NotificationServices">
<wsdl:port name="NotificationServicePort" binding="tns:NotificationServiceBinding">
<soap:address location="" />
</wsdl:port>
</wsdl:service>
So the full working PHP code is as follows:
class ewsService {
public function SendNotification( $arg ) {
$result = new EWSType_SendNotificationResultType();
$result->SubscriptionStatus = 'OK';
//$result->SubscriptionStatus = 'Unsubscribe';
return $result;
}
}
$server = new SoapServer( PHPEWS_PATH.'/wsdl/NotificationService.wsdl', array(
'uri' => $_SERVER['REQUEST_SCHEME'].'://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'],
));
$server->setObject( $service = new ewsService() );
$server->handle();
Which gives the following output:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://schemas.microsoft.com/exchange/services/2006/messages">
<SOAP-ENV:Body>
<ns1:SendNotificationResult>
<ns1:SubscriptionStatus>OK</ns1:SubscriptionStatus>
</ns1:SendNotificationResult>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Hope that helps someone else because that took me a while to figure out!

PHP SoapClient: How to prefix SOAP parameter tag name with namespace?

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

Why is PHP's SoapClient creating a different namespace for the header than the body?

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?

Categories