 |
| This is a discussion on Using Salesforce Outbound SOAP Messages with PHP within the Salesforce PHP Tutorials forums, part of the Salesforce category; I have corrected this code to handle the one to many relationship |

08-13-2008, 11:53 PM
|
|
Administrator
|
|
Join Date: May 2007
Posts: 273
|
|
I have corrected this code to handle the one to many relationship on outbound messages from Salesforce. I will post a generic version of the code sometime tomorrow
|

08-15-2008, 03:02 PM
|
|
Administrator
|
|
Join Date: May 2007
Posts: 273
|
|
When I first started working with outbound messaging in Salesforce, I was ecstatic that we could perform various actions based on a work flow rule, such as if a field was updated to a certain value (example: Won) on a custom object, we were able to create data in a new custom object that allows us track Design Win's.
Salesforce assigns an "ID" to each outbound message, to each Id of the object that is being used to send out the outbound message. I always thought that it was a 1 to 1 relationship, as I stated in an earlier post. This is not the case. There is a one to many relationship, which now has to be taken into consideration when writing the code to process your data coming in from Salesforce.
The following where the problem is, in the function PARSENOTIFICATION:
PHP Code:
function parseNotification($domDoc) { // Parse Notification parameters into result array $result = array("OrganizationId" => "","ActionId" => "","SessionId" => "","EnterpriseUrl" => "","PartnerUrl" => "","sObject" => null);
$result["OrganizationId"] = $domDoc->getElementsByTagName("OrganizationId")->item(0)->textContent; $result["ActionId"] = $domDoc->getElementsByTagName("ActionId")->item(0)->textContent; $result["SessionId"] = $domDoc->getElementsByTagName("SessionId")->item(0)->textContent; $result["EnterpriseUrl"] = $domDoc->getElementsByTagName("EnterpriseUrl")->item(0)->textContent; $result["PartnerUrl"] = $domDoc->getElementsByTagName("PartnerUrl")->item(0)->textContent;
// Create sObject and fill fields provided in notification $sObjectNode = $domDoc->getElementsByTagName("sObject")->item(0); $sObjType = $sObjectNode->getAttribute("type"); if (substr_count($sObjType,"sf:")) $sObjType = substr($sObjType,3); $result["sObject"] = new SObject($sObjType); $result["sObject"]->type = $sObjType;
$sObjectNodes = $domDoc->getElementsByTagNameNS('urn:sobject.enterprise.soap.sforce.com','*'); $result["sObject"]->fieldnames = array(); foreach ($sObjectNodes as $node) { if ($node->localName == "Id") { // Id goes under sObject->Id $result["sObject"]->Id = $node->textContent; } else { // ... the rest go under sObject->fields $fieldname = $node->localName; $result["sObject"]->fields->$fieldname = $node->nodeValue; array_push($result["sObject"]->fieldnames,$fieldname); } }
return $result; } // end parseNotification
The function above only allows for one record to process, not many! so if you saw the incoming example message, you will see what I mean by the 1 to many relationship.
I rewrote the function to process all of the incoming records and loop through to get all the data.
Here is the rewritten function:
PHP Code:
function parseNotification($domDoc) { // Parse Notification parameters into result array
$result = array("OrganizationId" => "","ActionId" => "","SessionId" => "","EnterpriseUrl" => "","PartnerUrl" => "","sObject" => null,"MapsRecords" => array());
$result["OrganizationId"] = $domDoc->getElementsByTagName("OrganizationId")->item(0)->textContent; $result["ActionId"] = $domDoc->getElementsByTagName("ActionId")->item(0)->textContent; $result["SessionId"] = $domDoc->getElementsByTagName("SessionId")->item(0)->textContent; $result["EnterpriseUrl"] = $domDoc->getElementsByTagName("EnterpriseUrl")->item(0)->textContent; $result["PartnerUrl"] = $domDoc->getElementsByTagName("PartnerUrl")->item(0)->textContent;
// Create sObject and fill fields provided in notification $sObjectNode = $domDoc->getElementsByTagName("sObject")->item(0); $sObjType = $sObjectNode->getAttribute("type"); if (substr_count($sObjType,"sf:")) { $sObjType = substr($sObjType,3); } $result["sObject"] = new SObject($sObjType); $result["sObject"]->type = $sObjType;
$sObjectNodes = $domDoc->getElementsByTagNameNS('urn:sobject.enterprise.soap.sforce.com','*'); $result["sObject"]->fieldnames = array(); $count = 0; $tempMapRecord = array(); foreach ($sObjectNodes as $node) {
if ($node->localName == "Id") { if ($count > 0) { $result["MapsRecords"][] = $tempMapRecord; $tempMapRecord = array(); } $tempMapRecord[$node->localName] = $node->textContent; } else { $tempMapRecord[$node->localName] = $node->textContent; }
$count++;
}
// Finish last item $result["MapsRecords"][] = $tempMapRecord; return $result; } // end parseNotification
So you would get the data back with use like this:
PHP Code:
$resultArray = parseNotification($dom); foreach ($resultArray['MapsRecords'] as $dwin_data) { //now you have an array as $dwin_data and you can use the //data as you need to for updates or calls back to salesforce //whatever you need to do is here }
When we create our Design Win custom object based on the outbound SOAP message, we do the following inside the foreach() loop:
- Query the Opportunity Object to get information needed for the Custom Object
- Query the Account Object to get information needed for the Custom Object
- Query the PriceBookEntry Object to get information needed for the Custom Object
- Query the User Object to get information needed for the Custom Object
- Build an array with all the required data
- perform a CREATE back to Salesforce to insert the new data into a new record within our custom object.
- Add a link back to the OpportunityLineItem for the new record that was created on the Design Win Object
It works perfectly!! Try the new function out and let me know if you have any issues with it.
~Mike
|

08-20-2008, 08:22 PM
|
|
Junior Member
|
|
Join Date: Aug 2008
Posts: 5
|
|
Hi. Apologies in advance for my newbieness. And thank you so much for this thread.. this is exactly what I'm trying to setup for my company. But i'm having trouble putting all the pieces together (and i know nothing about soap). So i'm hoping for a more complete picture of how to setup the files on my server.
I'm pretty sure my Outbound Messages are setup correctly within SF:
- i setup an Outbound Message, specified an Endpoint URL, and downloaded the wsdl.
- i setup a Workflow Rule, which finds something to send, which i can see in the queue (Administration Setup > Monitoring > Outbound Messages).
But i know i don't have things setup correctly on my server yet, so the attempted send fails.
The Queue shows:
Delivery Failure Reason "org.xml.sax.SAXException: Bad envelope tag: br".
And an error_log file is created on my server which says:
Warning: DOMDocument::loadXML() [domdocument.loadxml]: Empty string supplied as input in /home/xxx/html/dev/sf/outboundmsg/index.php on line 16
Fatal error: Call to a member function getAttribute() on a non-object in /home/xxx/html/dev/sf/outboundmsg/index.php on line 61
i'm unclear how to set things up on my server:
- i copied php code from this thread but don't understand how it all goes together in the file at my endpoint url. Any chance you could provide a complete php file?
- i replaced the parseNotification function with the update, but i'm unclear where to put the $resultArray = parseNotification($dom); foreach .
- where do i put the wsdl.xml file that i downloaded from SF?
- how/where do i point to the wsdl? Is there a config var somewhere that i missed?
- what to do with the Binding section (commented) in the wsdl from SF.
- any other editing/config of the php or wsdl that i need to do? Or additional code or files that i'm missing?
Also, one of the thread messages gave some php that included this:
/* Handles SFDC Outbound Message notification.*/
require_once ('../includes/salesforce_login.php');
but i'm not clear what that's for, or what exactly goes in that file.
I tried to figure it out from the SF help docs, but still couldn't find a simple straight-forward guide. Any help would be GREATLY appreciated.
Thanks in advance,
Todd
|

08-21-2008, 09:44 AM
|
|
Administrator
|
|
Join Date: May 2007
Posts: 273
|
|
Quote:
Originally Posted by toddz
Hi. Apologies in advance for my newbieness. And thank you so much for this thread.. this is exactly what I'm trying to setup for my company. But i'm having trouble putting all the pieces together (and i know nothing about soap). So i'm hoping for a more complete picture of how to setup the files on my server.
I'm pretty sure my Outbound Messages are setup correctly within SF:
- i setup an Outbound Message, specified an Endpoint URL, and downloaded the wsdl.
- i setup a Workflow Rule, which finds something to send, which i can see in the queue (Administration Setup > Monitoring > Outbound Messages).
But i know i don't have things setup correctly on my server yet, so the attempted send fails.
The Queue shows:
Delivery Failure Reason "org.xml.sax.SAXException: Bad envelope tag: br".
And an error_log file is created on my server which says:
Warning: DOMDocument::loadXML() [domdocument.loadxml]: Empty string supplied as input in /home/xxx/html/dev/sf/outboundmsg/index.php on line 16
Fatal error: Call to a member function getAttribute() on a non-object in /home/xxx/html/dev/sf/outboundmsg/index.php on line 61
i'm unclear how to set things up on my server:
- i copied php code from this thread but don't understand how it all goes together in the file at my endpoint url. Any chance you could provide a complete php file?
- i replaced the parseNotification function with the update, but i'm unclear where to put the $resultArray = parseNotification($dom); foreach .
- where do i put the wsdl.xml file that i downloaded from SF?
- how/where do i point to the wsdl? Is there a config var somewhere that i missed?
- what to do with the Binding section (commented) in the wsdl from SF.
- any other editing/config of the php or wsdl that i need to do? Or additional code or files that i'm missing?
Also, one of the thread messages gave some php that included this:
/* Handles SFDC Outbound Message notification.*/
require_once ('../includes/salesforce_login.php');
but i'm not clear what that's for, or what exactly goes in that file.
I tried to figure it out from the SF help docs, but still couldn't find a simple straight-forward guide. Any help would be GREATLY appreciated.
Thanks in advance,
Todd
|
Todd I am going to try and break this post down to see if I can answer it in parts
So there may be multiple posts to answer all your questions and I will try and help you as much as I can
First have you tried to create a script that just performs a log in to Salesforce to make sure it works?
When you download the phptoolkit, there is really only one relevant folder that is needed. the "/soapclient" folder
all the other folders are just tutorials and such.
when I develop apps, I always create an "/includes" folder for all my common scripts like the soapclient and that login script that you said you did not know what it was
The login script is a small script that was written so I can just include that and it takes care of my login so all I have to do is a
PHP Code:
require_once('./includes/salesforce_login_script.php')
Here is that script, so you can use it
PHP Code:
<?php
// change these paths to your fully qualified paths for each script to include require_once ('/users/msimonds/public_html/includes/soapclient/SforcePartnerClient.php'); require_once ('/users/msimonds/public_html/includes/soapclient/SforceHeaderOptions.php');
// Login to salesforce.com $login = "username"; $password = "password";
// change this paths to your fully qualified paths for the script to include $wsdl = "/users/msimonds/public_html/includes/soapclient/partner.wsdl.xml";
$client = new SforcePartnerClient(); $client->createConnection($wsdl); try { $loginResult = $client->login($login, $password); if ($loginResult->passwordExpired) { $client = null; } } catch (exception $e) { $client = null; } ?>
You need to make sure you are getting a connection to salesforce and that you can login
Hope that helps, once you get that part done, I will continue to help you get this setup
~Mike
|

08-21-2008, 07:53 PM
|
|
Junior Member
|
|
Join Date: Aug 2008
Posts: 5
|
|
Wow, thanks Mike.. tremendous help so far.
I now have SF triggering an OutboundMsg, which is received at my EndpointURL (index.php), which writes some xml to "soap.xml".
Here's my server so far:
/dev/sf/outboundmsg/index.php (code from this thread)
/dev/sf/outboundmsg/my_wsdl.xml (from my SF OutboundMsg setup)
/dev/sf/outboundmsg/includes/soapclient/ (from phptoolkit-11_0b)
/dev/sf/outboundmsg/includes/salesforce_login_script.php (with my paths and login)
I'm ready for the next step!
I assume i need to edit index.php?
And do something with my custom WSDL from SF?
Thanks again. This is very exciting.
Todd
|

08-21-2008, 10:54 PM
|
|
Administrator
|
|
Join Date: May 2007
Posts: 273
|
|
Hope this works
|
|
Quote:
Originally Posted by toddz
Wow, thanks Mike.. tremendous help so far.
I now have SF triggering an OutboundMsg, which is received at my EndpointURL (index.php), which writes some xml to "soap.xml".
Here's my server so far:
/dev/sf/outboundmsg/index.php (code from this thread)
/dev/sf/outboundmsg/my_wsdl.xml (from my SF OutboundMsg setup)
/dev/sf/outboundmsg/includes/soapclient/ (from phptoolkit-11_0b)
/dev/sf/outboundmsg/includes/salesforce_login_script.php (with my paths and login)
I'm ready for the next step!
I assume i need to edit index.php?
And do something with my custom WSDL from SF?
Thanks again. This is very exciting.
Todd
|
The following code could replace your index.php and be used to see if you are at least getting the data in an email
look at this part of the code and make sure you change the email address that I have clearly marked in the comments
PHP Code:
<?php error_reporting(0);
$data = fopen('php://input','rb');
$content = stream_get_contents($data);
if ($content) { respond('true'); } else { respond('false'); }
//login script to salesforce for the $client variable ***change this path require_once ('/users/msimonds/public_html/includes/sfdc.inc');
// setup an array for the data coming in from the SOAP message $dwin_data = array();
$dom = new DOMDocument(); $dom->loadXML($content); $resultArray = parseNotification($dom); unset($resultArray['sObject']);
// Data from SOAP message. /*************************************************** * This is where you do all your coding * * and process the incomingin record as * * need. use the code: * * $temp = '<pre>' . print_r($var,true) . '</pre>'; * * mail('youremail@address.com', 'Suject', $temp); * * * * To test to make sure you are getting the * * data in an email, change the email address * ****************************************************/ foreach ($resultArray['MapsRecords'] as $dwin_data) { //make sure you are getting the email with the data in //a print_r and then we can go from there $temp = '<pre>' . print_r($var,true) . '</pre>'; mail('youremail@address.com', 'Suject', $temp); }
//clean all the data and then exit unset($client); unset($dwin_data); unset($dwin_create); unset($records); exit;
//functions /* Parse a Salesforce.com Outbound Message notification SOAP packet * into an array of notification parms and an sObject. */ function parseNotification($domDoc) { // Parse Notification parameters into result array
$result = array("OrganizationId" => "","ActionId" => "","SessionId" => "","EnterpriseUrl" => "","PartnerUrl" => "","sObject" => null,"MapsRecords" => array());
$result["OrganizationId"] = $domDoc->getElementsByTagName("OrganizationId")->item(0)->textContent; $result["ActionId"] = $domDoc->getElementsByTagName("ActionId")->item(0)->textContent; $result["SessionId"] = $domDoc->getElementsByTagName("SessionId")->item(0)->textContent; $result["EnterpriseUrl"] = $domDoc->getElementsByTagName("EnterpriseUrl")->item(0)->textContent; $result["PartnerUrl"] = $domDoc->getElementsByTagName("PartnerUrl")->item(0)->textContent;
// Create sObject and fill fields provided in notification $sObjectNode = $domDoc->getElementsByTagName("sObject")->item(0); $sObjType = $sObjectNode->getAttribute("type"); if (substr_count($sObjType,"sf:")) { $sObjType = substr($sObjType,3); } $result["sObject"] = new SObject($sObjType); $result["sObject"]->type = $sObjType;
$sObjectNodes = $domDoc->getElementsByTagNameNS('urn:sobject.enterprise.soap.sforce.com','*'); $result["sObject"]->fieldnames = array(); $count = 0; $tempMapRecord = array(); foreach ($sObjectNodes as $node) { if ($node->localName == "Id") { if ($count > 0) { $result["MapsRecords"][] = $tempMapRecord; $tempMapRecord = array(); } $tempMapRecord[$node->localName] = $node->textContent; } else { $tempMapRecord[$node->localName] = $node->textContent; } $count++; }
// Finish last item $result["MapsRecords"][] = $tempMapRecord;
return $result; } // end parseNotification
function respond($tf) {
print '<?xml version = "1.0" encoding = "utf-8"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <soapenv:Body> <notifications xmlns="http://soap.sforce.com/2005/09/outbound"> <Ack>'.$tf.'</Ack> </notifications> </soapenv:Body> </soapenv:Envelope>'; }
//this is the funciton to query salesforce objects to gather data //from different fields coming in from the SOAP message function get_records(&$connection,&$query) { $queryOptions = new QueryOptions(2000);
$response = new QueryResult($connection->query($query)); // if the size is zero, where done if ($response->size > 0) { $products = $response->records; // Cycles through additional responses if the number of records // exceeds the batch size while (!$response->done) { set_time_limit(100); $response = $connection->queryMore($response->queryLocator); $products = array_merge($products,$response->records); } } return $products; }
?>
this should be the next step
~Mike
|

08-22-2008, 04:58 PM
|
|
Junior Member
|
|
Join Date: Aug 2008
Posts: 5
|
|
Hi Mike, thanks for the continued help here.
I am getting the email now, but it does not have the soap data yet. The email only shows:
I'm not clear how $var is defined in..
Code:
foreach ($resultArray['MapsRecords'] as $dwin_data)
{
$temp = '<pre>' . print_r($var,true) . '</pre>';
mail('my@email.com', 'soap from sf', $temp);
}
Do i need to change something in there?
And to verify that my OutboundMessage does contain data, i set index.php to log to a local file (soap.xml) again (instead of sending an email), and i still get the full soap xml, including fields and values data.
sorry if i'm missing something here. thanks in advance.
todd
|

08-22-2008, 06:57 PM
|
|
Administrator
|
|
Join Date: May 2007
Posts: 273
|
|
Crap! My Bad
PHP Code:
foreach ($resultArray['MapsRecords'] as $dwin_data) { //make sure you are getting the email with the data in //a print_r and then we can go from there $temp = '<pre>' . print_r($var,true) . '</pre>'; mail('youremail@address.com', 'Suject', $temp); }
SHOULD BE
PHP Code:
foreach ($resultArray['MapsRecords'] as $dwin_data) { //make sure you are getting the email with the data in //a print_r and then we can go from there $temp = '<pre>' . print_r($dwin_data,true) . '</pre>'; mail('youremail@address.com', 'Suject', $temp); }
that way the $resultArray becomes the $dwin_data array in the foreach loop and you should get an email that has each element in the array with its value
Sorry about that and let me know if it works
~Mike
|

08-22-2008, 07:26 PM
|
|
| |