Go Back   Mike Simonds > Salesforce > Salesforce PHP Tutorials

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

Reply
 
LinkBack (2) Thread Tools Rate Thread
  #11  
Old 08-13-2008, 11:53 PM
Administrator
 
Join Date: May 2007
Posts: 273
Send a message via AIM to mike Send a message via MSN to mike Send a message via Yahoo to mike Send a message via Skype™ to mike

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
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote

  #12  
Old 08-15-2008, 03:02 PM
Administrator
 
Join Date: May 2007
Posts: 273
Send a message via AIM to mike Send a message via MSN to mike Send a message via Yahoo to mike Send a message via Skype™ to mike

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:

  1. Query the Opportunity Object to get information needed for the Custom Object
  2. Query the Account Object to get information needed for the Custom Object
  3. Query the PriceBookEntry Object to get information needed for the Custom Object
  4. Query the User Object to get information needed for the Custom Object
  5. Build an array with all the required data
  6. perform a CREATE back to Salesforce to insert the new data into a new record within our custom object.
  7. 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
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote
  #13  
Old 08-20-2008, 08:22 PM
Junior Member
 
Join Date: Aug 2008
Posts: 5
php / wsdl help?

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
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote
  #14  
Old 08-21-2008, 09:44 AM
Administrator
 
Join Date: May 2007
Posts: 273
Send a message via AIM to mike Send a message via MSN to mike Send a message via Yahoo to mike Send a message via Skype™ to mike

Quote:
Originally Posted by toddz View Post
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
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote
  #15  
Old 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
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote
  #16  
Old 08-21-2008, 10:54 PM
Administrator
 
Join Date: May 2007
Posts: 273
Send a message via AIM to mike Send a message via MSN to mike Send a message via Yahoo to mike Send a message via Skype™ to mike
Talking Hope this works

Quote:
Originally Posted by toddz View Post
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
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote
  #17  
Old 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:
Code:
 <pre></pre>
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
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote
  #18  
Old 08-22-2008, 06:57 PM
Administrator
 
Join Date: May 2007
Posts: 273
Send a message via AIM to mike Send a message via MSN to mike Send a message via Yahoo to mike Send a message via Skype™ to mike

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
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
Reply With Quote
  #19  
Old 08-22-2008, 07:26 PM
Junior Member