You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
If you are trying to use this bundle for a PARTNER type wsdl, as I ultimately did, here's a way to go about it. It would be great if this bundle did this natively.
I went this route because a.) I didn't like the bloat from BeSimple and Zendframework with another client bundle, and b.) I needed Partner WSDL support, not Enterprise.
First, extend the Phpforce client in you own bundle. I chose to keep my phpforce extended and supporting classes in a subdirectory called Phpforce. Notable aspects of this extension are an override of the SOAP_NAMESPACE constant and an override of all methods that use this constant. But also of crucial importance is the alteration to the createSoapVars function, wherein the "any" field is properly processed on an SObject.
One other note is that you'll see we're not setting the username/password/apiKey in the constructor. If you're using the Partner wsdl, flexibility in credentials seems to me to be one of the main reasons. Here I'll be using an entity called SalesforceCredentials to manage those login settings. Achieve the same thing however you want.
<?phpnamespaceAcme\SalesforceBundle\Phpforce;
useAcme\SalesforceBundle\Entity\SalesforceCredentials;
usePhpforce\SoapClient\Soap\SoapClient;
usePhpforce\SoapClient\ClientasBaseClient;
class Client extends BaseClient
{
constSOAP_NAMESPACE = 'urn:partner.soap.sforce.com';
publicfunction__construct(SoapClient$soapClient)
{
parent::__construct($soapClient, null, null, null);
}
publicfunctionsetCredentials(SalesforceCredentials$salesforce) {
$this->username = $salesforce->getUsername();
$this->password = $salesforce->getPassword();
$this->token = $salesforce->getApiKey();
}
publicfunctionlogin($username, $password, $token)
{
try {
$result = $this->soapClient->login(array(
'username' => $username,
'password' => $password.$token
));
} catch (\SoapFault$soapFault) {
return$soapFault;
}
$this->setLoginResult($result->result);
return$result->result;
}
publicfunctiongetLoginResult()
{
if (null === $this->loginResult) {
$loginResult = $this->login($this->username, $this->password, $this->token);
if (get_class($loginResult) == 'SoapFault') {
return$loginResult;
}
}
return$this->loginResult;
}
publicfunctionmerge(array$mergeRequests, $type)
{
foreach ($mergeRequestsas$mergeRequest) {
if (!($mergeRequestinstanceofRequest\MergeRequest)) {
thrownew \InvalidArgumentException(
'Each merge request must be an instance of MergeRequest'
);
}
if (!$mergeRequest->masterRecord || !is_object($mergeRequest->masterRecord)) {
thrownew \InvalidArgumentException('masterRecord must be an object');
}
if (!$mergeRequest->masterRecord->Id) {
thrownew \InvalidArgumentException('Id for masterRecord must be set');
}
if (!is_array($mergeRequest->recordToMergeIds)) {
thrownew \InvalidArgumentException('recordToMergeIds must be an array');
}
$mergeRequest->masterRecord = new \SoapVar(
$this->createSObject($mergeRequest->masterRecord, $type),
SOAP_ENC_OBJECT,
$type,
self::SOAP_NAMESPACE
);
}
return$this->call(
'merge',
array('request' => $mergeRequests)
);
}
protectedfunctioncreateSoapVars(array$objects, $type)
{
$soapVars = array();
foreach ($objectsas$object) {
$sObject = $this->createSObject($object, $type);
// REALLY IMPORTANT ALTERATION. (PARTNER WSDL COMPATIBILITY).if (isset($sObject->any)) {
foreach ($sObject->anyas$key => $value) {
$sObject->{$key} = $value;
}
unset($sObject->any);
}
$xml = '';
if (isset($sObject->fieldsToNull)) {
foreach ($sObject->fieldsToNullas$fieldToNull) {
$xml .= '<fieldsToNull>' . $fieldToNull . '</fieldsToNull>';
}
$fieldsToNullVar = new \SoapVar(new \SoapVar($xml, XSD_ANYXML), SOAP_ENC_ARRAY);
$sObject->fieldsToNull = $fieldsToNullVar;
}
$soapVar = new \SoapVar($sObject, SOAP_ENC_OBJECT, $type, self::SOAP_NAMESPACE);
$soapVars[] = $soapVar;
}
return$soapVars;
}
protectedfunctionsetSoapHeaders(array$headers)
{
$soapHeaderObjects = array();
foreach ($headersas$key => $value) {
$soapHeaderObjects[] = new \SoapHeader(self::SOAP_NAMESPACE, $key, $value);
}
$this->soapClient->__setSoapHeaders($soapHeaderObjects);
}
protectedfunctionsetSessionId($sessionId)
{
$this->sessionHeader = new \SoapHeader(
self::SOAP_NAMESPACE,
'SessionHeader',
array(
'sessionId' => $sessionId
)
);
}
}
The Partner API is going to respond to queries with an "all" key full of namespaced xml, which needs to be converted. Processing those to a proper SObject requires borrowing a class from the Salesforce PHP Toolkit.
<?php/* * This class taken from the Official PHP Salesforce Toolkit. * Added preceding slash to stdClass for compatibility * https://github.com/developerforce/Force.com-Toolkit-for-PHP/blob/master/soapclient/SforceBaseClient.php */namespaceAcme\SalesforceBundle\Phpforce;
class SObject {
public$type;
public$fields;
// public $sobject;publicfunction__construct($response=NULL) {
if (!isset($response) && !$response) {
return;
}
foreach ($responseas$responseKey => $responseValue) {
if (in_array($responseKey, array('Id', 'type', 'any'))) {
continue;
}
$this->$responseKey = $responseValue;
}
if (isset($response->Id)) {
$this->Id = is_array($response->Id) ? $response->Id[0] : $response->Id;
}
if (isset($response->type)) {
$this->type = $response->type;
}
if (isset($response->any)) {
try {
//$this->fields = $this->convertFields($response->any);// If ANY is an object, instantiate another SObjectif ($response->anyinstanceof stdClass) {
if ($this->isSObject($response->any)) {
$anArray = array();
$sobject = newSObject($response->any);
$anArray[] = $sobject;
$this->sobjects = $anArray;
} else {
// this is for parent to child relationships$this->queryResult = newQueryResult($response->any);
}
} else {
// If ANY is an arrayif (is_array($response->any)) {
// Loop through each and perform some action.$anArray = array();
// Modify the foreach to have $key=>$value// Added on 28th April 2008foreach ($response->anyas$key=>$item) {
if ($iteminstanceof stdClass) {
if ($this->isSObject($item)) {
$sobject = newSObject($item);
// make an associative array instead of a numeric one$anArray[$key] = $sobject;
} else {
// this is for parent to child relationships//$this->queryResult = new QueryResult($item);if (!isset($this->queryResult)) {
$this->queryResult = array();
}
array_push($this->queryResult, newQueryResult($item));
}
} else {
//$this->fields = $this->convertFields($item);if (strpos($item, 'sf:') === false) {
$currentXmlValue = sprintf('<sf:%s>%s</sf:%s>', $key, $item, $key);
} else {
$currentXmlValue = $item;
}
if (!isset($fieldsToConvert)) {
$fieldsToConvert = $currentXmlValue;
} else {
$fieldsToConvert .= $currentXmlValue;
}
}
}
if (isset($fieldsToConvert)) {
// If this line is commented, then the fields becomes a stdclass object and does not have the name variable// In this case the foreach loop on line 252 runs successfuly$this->fields = $this->convertFields($fieldsToConvert);
}
if (sizeof($anArray) > 0) {
// To add more variables to the the top level sobjectforeach ($anArrayas$key=>$children_sobject) {
$this->fields->$key = $children_sobject;
}
//array_push($this->fields, $anArray);// Uncommented on 28th April since all the sobjects have now been moved to the fields//$this->sobjects = $anArray;
}
/* $this->fields = $this->convertFields($response->any[0]); if (isset($response->any[1]->records)) { $anArray = array(); if ($response->any[1]->size == 1) { $records = array ( $response->any[1]->records ); } else { $records = $response->any[1]->records; } foreach ($records as $record) { $sobject = new SObject($record); array_push($anArray, $sobject); } $this->sobjects = $anArray; } else { $anArray = array(); $sobject = new SObject($response->any[1]); array_push($anArray, $sobject); $this->sobjects = $anArray; } */
} else {
$this->fields = $this->convertFields($response->any);
}
}
} catch (Exception$e) {
var_dump('exception: ', $e);
}
}
}
function__get($name) { return (isset($this->fields->$name))? $this->fields->$name : false; }
function__isset($name) { returnisset($this->fields->$name); }
/** * Parse the "any" string from an sObject. First strip out the sf: and then * enclose string with <Object></Object>. Load the string using * simplexml_load_string and return an array that can be traversed. */functionconvertFields($any) {
$str = preg_replace('{sf:}', '', $any);
$array = $this->xml2array('<Object xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'.$str.'</Object>', 2);
$xml = new \stdClass();
if (!count($array['Object']))
return$xml;
foreach ($array['Object'] as$k=>$v) {
$xml->$k = $v;
}
//$new_string = '<Object xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'.$new_string.'</Object>';//$new_string = $new_string;//$xml = simplexml_load_string($new_string);return$xml;
}
/** * * @param string $contents * @return array */functionxml2array($contents, $get_attributes=1) {
if(!$contents) returnarray();
if(!function_exists('xml_parser_create')) {
//print "'xml_parser_create()' function not found!";returnarray('not found');
}
//Get the XML parser of PHP - PHP must have this module for the parser to work$parser = xml_parser_create();
xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, 0 );
xml_parser_set_option( $parser, XML_OPTION_SKIP_WHITE, 1 );
xml_parse_into_struct( $parser, $contents, $xml_values );
xml_parser_free( $parser );
if(!$xml_values) return;//Hmm...//Initializations$xml_array = array();
$parents = array();
$opened_tags = array();
$arr = array();
$current = &$xml_array;
//Go through the tags.foreach($xml_valuesas$data) {
unset($attributes,$value);//Remove existing values, or there will be trouble//This command will extract these variables into the foreach scope// tag(string), type(string), level(int), attributes(array).extract($data);//We could use the array by itself, but this cooler.$result = '';
if ($get_attributes) {
switch ($get_attributes) {
case1:
$result = array();
if(isset($value)) $result['value'] = $value;
//Set the attributes too.if(isset($attributes)) {
foreach($attributesas$attr => $val) {
if($get_attributes == 1) $result['attr'][$attr] = $val; //Set all the attributes in a array called 'attr'/** :TODO: should we change the key name to '_attr'? Someone may use the tagname 'attr'. Same goes for 'value' too */
}
}
break;
case2:
$result = array();
if (isset($value)) {
$result = $value;
}
//Check for nil and ignore other attributes.if (isset($attributes) && isset($attributes['xsi:nil']) && !strcasecmp($attributes['xsi:nil'], 'true')) {
$result = null;
}
break;
}
} elseif (isset($value)) {
$result = $value;
}
//See tag status and do the needed.if($type == "open") {//The starting of the tag '<tag>'$parent[$level-1] = &$current;
if(!is_array($current) or (!in_array($tag, array_keys($current)))) { //Insert New tag$current[$tag] = $result;
$current = &$current[$tag];
} else { //There was another element with the same tag nameif(isset($current[$tag][0])) {
array_push($current[$tag], $result);
} else {
$current[$tag] = array($current[$tag],$result);
}
$last = count($current[$tag]) - 1;
$current = &$current[$tag][$last];
}
} elseif($type == "complete") { //Tags that ends in 1 line '<tag />'//See if the key is already taken.if(!isset($current[$tag])) { //New Key$current[$tag] = $result;
} else { //If taken, put all things inside a list(array)if((is_array($current[$tag]) and$get_attributes == 0)//If it is already an array...or (isset($current[$tag][0]) andis_array($current[$tag][0]) and ($get_attributes == 1 || $get_attributes == 2))) {
array_push($current[$tag],$result); // ...push the new element into that array.
} else { //If it is not an array...$current[$tag] = array($current[$tag],$result); //...Make it an array using using the existing value and the new value
}
}
} elseif($type == 'close') { //End of tag '</tag>'$current = &$parent[$level-1];
}
}
return($xml_array);
}
/* * If the stdClass has a done, we know it is a QueryResult */functionisQueryResult($param) {
returnisset($param->done);
}
/* * If the stdClass has a type, we know it is an SObject */functionisSObject($param) {
returnisset($param->type);
}
}
Next turn this into a service in your service.xml. I did elect to store the wsdl locally as Acme/SalesforceBundle/Phpforce/partner.wsdl.xml, reflected here.
All right, so now a few important things in a controller. Login (successfully), do a query, and do an update.
//... Some containeraware controller method//... or pass @acme.salesforce_client to another service as an argumentuseAcme\SalesforceBundle\Phpforce\SObject;
$this->client = $this->container->get('acme.salesforce_client');
$this->client->setCredentials($salesforceCredentials);
$loginResult = $this->client->getLoginResult();
if (get_class($loginResult) == 'SoapFault') {
// $error_string = $loginResult->getMessage();// add $error_string to FlashBag or whatever.
}
else {
// First will query for a Lead object with xyz email address.// But we'll also ask for all fields, including custom, that we got when we did a describe call on the Lead.$email = '[email protected]';
$query = 'select Id, isConverted, isDeleted';
foreach($filtered_field_array_taken_from_describe['fields'] as$field) {
$query .= ', ' . $field['name'];
}
$query .= ' from Lead where Email = \'' . $email . '\' limit 5';
$result = $this->client->query($query);
foreach ($resultas$record) {
$sObject = newSObject($record);
// Now do what you will with fields, like $sObject->fields->IsConverted;// Or what you will with $record->getId(); (which might be an array)
}
// Now an update call// Here updating a lead, with fake $postData from wherever.// the $Id presumably would come from the query above.$lead = new \stdClass();
$lead->Id = $Id;
$lead->type = 'Lead';
$lead->any = new \stdClass();
$postData = array('FirstName' => 'Joe', 'LastName' => 'Smith');
foreach ($postDataas$key => $value) {
$lead->any->$key = $value;
}
$response = $this->client->update(array($lead), 'sObject');
}
//...
Thanks for the great Salesforce bundles, David.
The text was updated successfully, but these errors were encountered:
Hi Bart,
Thanks for your input on this!
On my side I extended the Client and ClientBuilder and did and overwrite of your createSoapVars and changed the SOAP_NAMESPACE
Saved me some painful hassle
If you are trying to use this bundle for a PARTNER type wsdl, as I ultimately did, here's a way to go about it. It would be great if this bundle did this natively.
I went this route because a.) I didn't like the bloat from BeSimple and Zendframework with another client bundle, and b.) I needed Partner WSDL support, not Enterprise.
First, extend the Phpforce client in you own bundle. I chose to keep my phpforce extended and supporting classes in a subdirectory called Phpforce. Notable aspects of this extension are an override of the SOAP_NAMESPACE constant and an override of all methods that use this constant. But also of crucial importance is the alteration to the createSoapVars function, wherein the "any" field is properly processed on an SObject.
One other note is that you'll see we're not setting the username/password/apiKey in the constructor. If you're using the Partner wsdl, flexibility in credentials seems to me to be one of the main reasons. Here I'll be using an entity called SalesforceCredentials to manage those login settings. Achieve the same thing however you want.
The Partner API is going to respond to queries with an "all" key full of namespaced xml, which needs to be converted. Processing those to a proper SObject requires borrowing a class from the Salesforce PHP Toolkit.
Next turn this into a service in your service.xml. I did elect to store the wsdl locally as Acme/SalesforceBundle/Phpforce/partner.wsdl.xml, reflected here.
All right, so now a few important things in a controller. Login (successfully), do a query, and do an update.
Thanks for the great Salesforce bundles, David.
The text was updated successfully, but these errors were encountered: