comsave/salesforce-outbound-message-bundle
Install Dependencies
composer require comsave/salesforce-outbound-message-bundle comsave/salesforce-mapper-bundle doctrine/mongodb-odm
Ensure MongoDB is configured in your config/packages/doctrine_mongodb_odm.yaml.
Register the Bundle
Add to config/bundles.php:
Comsave\SalesforceOutboundMessageBundle\ComsaveSalesforceOutboundMessageBundle::class => ['all' => true],
Configure Salesforce Mapping
Use comsave/salesforce-mapper-bundle to define how Salesforce objects map to your MongoDB Document classes. Example:
# config/packages/comsave_salesforce_mapper.yaml
comsave_salesforce_mapper:
mappings:
Lead:
class: App\Entity\Lead
fields:
- id
- first_name
- last_name
First Use Case: Handling an Outbound Message
Configure a listener to process incoming Salesforce outbound messages. Example in config/packages/comsave_salesforce_outbound_message.yaml:
comsave_salesforce_outbound_message:
listeners:
Lead:
class: App\EventListener\SalesforceLeadListener
method: onLeadUpdate
Define Listeners
Create event listeners for specific Salesforce objects (e.g., Lead, Account). Example:
// src/EventListener/SalesforceLeadListener.php
namespace App\EventListener;
use Comsave\SalesforceOutboundMessageBundle\Event\SalesforceOutboundMessageEvent;
use App\Entity\Lead;
class SalesforceLeadListener
{
public function onLeadUpdate(SalesforceOutboundMessageEvent $event)
{
$data = $event->getData();
$lead = new Lead();
$lead->hydrate($data); // Assuming hydrate() is defined in your Document
$event->getDocumentManager()->persist($lead);
}
}
Leverage beforeFlush and afterFlush
Customize behavior before/after processing:
# config/packages/comsave_salesforce_outbound_message.yaml
comsave_salesforce_outbound_message:
listeners:
Lead:
class: App\EventListener\SalesforceLeadListener
method: onLeadUpdate
before_flush: true # Optional: Run logic before persisting
after_flush: true # Optional: Run logic after persisting
Handle Deletes Enable deletion support by following README-setup-removal.md. Example listener:
public function onLeadDelete(SalesforceOutboundMessageEvent $event)
{
$id = $event->getData()['Id'];
$lead = $event->getDocumentManager()->find(Lead::class, $id);
if ($lead) {
$event->getDocumentManager()->remove($lead);
}
}
Batch Processing For high-volume messages, implement batch processing in your listener:
public function onBulkLeadUpdate(SalesforceOutboundMessageEvent $event)
{
$dm = $event->getDocumentManager();
$batch = $event->getData();
foreach ($batch as $leadData) {
$lead = new Lead();
$lead->hydrate($leadData);
$dm->persist($lead);
}
$dm->flush();
}
Integration with Symfony Messenger Offload processing to a queue for async handling:
# config/packages/messenger.yaml
framework:
messenger:
transports:
salesforce: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
'Comsave\SalesforceOutboundMessageBundle\Message\SalesforceOutboundMessage': salesforce
MongoDB ID Mismatch
Id fields map to MongoDB _id or a custom field. Example:
// In your Document class
/**
* @MongoDB\Id(strategy="UUID")
* @MongoDB\Field(type="string")
*/
private $salesforceId;
comsave/salesforce-mapper-bundle to explicitly map Salesforce IDs.Circular References
Contact under Account). MongoDB ODM may fail with circular references.@MongoDB\ReferenceOne or @MongoDB\EmbedOne annotations carefully, or flatten nested data in your listener.Duplicate Processing
create/update calls if not handled idempotently.Id in your MongoDB collection or implement a deduplication check in your listener:
if (!$dm->getRepository(Lead::class)->findOneBy(['salesforceId' => $data['Id']])) {
$dm->persist($lead);
}
Listener Ordering
comsave_salesforce_outbound_message.yaml:
listeners:
Lead:
class: App\EventListener\LeadPriorityListener
method: onLeadUpdate
priority: 100 # Higher priority runs first
Timezone Issues
beforeFlush/afterFlush logic to fail.$data['CreatedDate'] = (new \DateTime($data['CreatedDate']))->setTimezone(new \DateTimeZone('UTC'));
Enable Verbose Logging Configure Monolog to log outbound message events:
# config/packages/monolog.yaml
monolog:
handlers:
salesforce:
type: stream
path: "%kernel.logs_dir%/salesforce_outbound.log"
level: debug
channels: ["salesforce"]
Then log in your listener:
$this->logger->debug('Processing Salesforce data', ['data' => $event->getData()]);
Validate Incoming Data Use Symfony’s Validator to validate Salesforce payloads:
use Symfony\Component\Validator\Validator\ValidatorInterface;
public function __construct(ValidatorInterface $validator)
{
$this->validator = $validator;
}
public function onLeadUpdate(SalesforceOutboundMessageEvent $event)
{
$errors = $this->validator->validate($event->getData());
if (count($errors) > 0) {
throw new \RuntimeException('Invalid Salesforce data: ' . (string) $errors);
}
// Process data...
}
Test with Mock Data
Unit test listeners with mock SalesforceOutboundMessageEvent:
use Comsave\SalesforceOutboundMessageBundle\Event\SalesforceOutboundMessageEvent;
use Doctrine\ODM\MongoDB\DocumentManager;
$dm = $this->createMock(DocumentManager::class);
$event = new SalesforceOutboundMessageEvent($dm, ['Id' => '001xxxx', 'Name' => 'Test Lead']);
$listener = new SalesforceLeadListener();
$listener->onLeadUpdate($event);
Custom Event Subscribers Extend the bundle’s event system by creating custom subscribers:
use Comsave\SalesforceOutboundMessageBundle\Event\SalesforceOutboundMessageEvents;
class CustomSalesforceSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
SalesforceOutboundMessageEvents::PRE_PROCESS => 'onPreProcess',
];
}
public function onPreProcess(SalesforceOutboundMessageEvent $event)
{
// Pre-process logic (e.g., enrich data)
}
}
Dynamic Mapping Override default mappings at runtime:
$event->setMapper(new CustomSalesforceMapper($event->getData()));
Webhook Validation Validate Salesforce webhook signatures if using HTTPS endpoints:
use Comsave\SalesforceOutboundMessageBundle\Security\SalesforceSignatureValidator;
$validator = new SalesforceSignatureValidator('your_webhook_secret');
if (!$validator->isValid($request)) {
throw new \RuntimeException('Invalid Salesforce webhook signature');
}
Async Processing with Doctrine Events Use Doctrine lifecycle events for async operations:
$
How can I help you explore Laravel packages today?