maillotf/salesforce-bridge-bundle
A Symfony bundle that bridges your application with Salesforce, simplifying integration so you can connect services, sync data, and work with Salesforce APIs from a familiar PHP/Symfony setup.
This package is a Symfony bundle, not a Laravel package. The first thing to check before installing is whether it's actually usable in your Laravel app or whether you need a Symfony project (or an adapter layer). If you're committed to using it in Laravel:
Install the dependency:
composer require maillotf/salesforce-bridge-bundle
Register the service provider manually. The bundle's README assumes Symfony's bundle auto-registration (config/bundles.php). In Laravel you'll need to add the bundle to config/app.php (or the bootstrap/providers.php on Laravel 11+) and publish/translate the framework.yaml config into a Laravel config file:
// config/salesforce.php
return [
'authentication' => [
'endpoint' => env('SF_LOGIN_URL', 'https://login.salesforce.com/'),
'client_id' => env('SF_CLIENT_ID'),
'client_secret' => env('SF_CLIENT_SECRET'),
'username' => env('SF_USERNAME'),
'password' => env('SF_PASSWORD'),
'security_token'=> env('SF_SECURITY_TOKEN'),
],
'rest' => [
'version' => 'v35.0',
'endpoint' => env('SF_INSTANCE_URL'),
],
];
Bind the service in a provider so the container resolves it the same way Symfony's services.yaml alias does:
$this->app->singleton(\MaillotF\Salesforce\SalesforceBridgeBundle\Service\SalesforceService::class);
First use case: fetch a record by Salesforce ID and branch on the response:
use MaillotF\Salesforce\SalesforceBridgeBundle\Service\SalesforceService;
public function show(SalesforceService $salesforce)
{
$response = $salesforce->getBySobjectId('Contact', '0035J000001fqm1QAA');
return $response->isSuccess()
? response()->json($response->getData())
: response()->json(['error' => 'Not Found'], 404);
}
Start by looking at the bundle's Service/SalesforceService class and its response wrapper (isSuccess()) — that's the primary surface you'll touch day to day.
Wrap the client behind an application service. Don't inject SalesforceService into controllers directly. Create a thin app-level service so you can swap client implementation later (the bundle's API version is old — see Gotchas):
class SalesforceRepository
{
public function __construct(private SalesforceService $client) {}
public function findContact(string $id): ?array
{
$res = $this->client->getBySobjectId('Contact', $id);
return $res->isSuccess() ? $res->getData() : null;
}
}
Credentials via environment, always. The bundle's required fields include client_id, client_secret, username, password, and security_token. Put all of them in .env and reference them in config/salesforce.php. Never commit the security token — it's the most frequently leaked value in this kind of setup.
Queue Salesforce writes. getBySobjectId is shown in the README, but writes (create/update/delete) are HTTP-bound and rate-limited by Salesforce. Dispatch a job from your controller and perform the call in the worker so a slow Salesforce response doesn't tie up a request:
SyncContactToSalesforce::dispatch($contact->id)->onQueue('salesforce');
The client can be resolved in the job's handle() via the container, same as in a controller.
Response handling — treat isSuccess() as the guard, not the exception path. The package returns a response object rather than throwing on non-2xx. Normalize at one boundary so the rest of your app deals with arrays and exceptions, not raw responses:
if (!$response->isSuccess()) {
throw new SalesforceException($response->getStatusCode(), $response->getMessage());
}
Multi-instance / sandbox support. If you need to hit sandboxes alongside production, keep a second config key (salesforce.sandboxes.*) and build the service by hand from config rather than the singleton alias — the Symfony alias style here doesn't naturally support named connections.
Feature-test with a stubbed binding. Because everything goes through one class, you can bind a fake in a test:
$this->app->bind(SalesforceService::class, fn () => new FakeSalesforceService());
This is a Symfony bundle — the biggest gotcha. framework.yaml and services.yaml don't exist in Laravel; those are Symfony 4 paths. The bundle is tagged for Symfony 4 / PHP 7. Symfony 4 reached EOL in 2021, and PHP 7.4 reached EOL in 2022 — this is a significant maintenance warning. It may still work, but you're running unsupported dependencies. Evaluate whether the underlying Symfony HTTP client it pulls in conflicts with Laravel's newer dependencies; composer resolution may fail on PHP 8.1+ or Symfony 6+ constraints.
API version v35.0 is stale. That's a Salesforce REST API version from 2015. Many newer objects and fields (especially anything added in the last ~9 years) will 400 on this version. If your use case needs current objects, override the rest.version config to a contemporary version and verify the response shape — this package makes assumptions about a few response fields that could change.
Username/password auth is deprecated. Salesforce has been pushing OAuth 2.0 / JWT bearer flow for years. Password+security-token auth is still supported in many orgs but is subject to org-level policies (session timeouts, IP restrictions) and can be disabled org-wide. Expect this to break the moment an admin toggles "Enforce OAuth" or your security-token IP range doesn't include the server. Keep the login endpoint configurable so you can point at a different instance.
security_token confusion. A common debugging issue: it's appended to the password, not sent as its own header. If auth fails with INVALID_LOGIN, first check that the token is present and from the correct user — resetting a password invalidates the security token, and the error message rarely says so.
Session reuse. Username/password auth issues a session that expires (typically ~2 hours, configurable per org). If the bundle doesn't refresh internally, long-running queue workers will start getting INVALID_SESSION_ID mid-job. Either configure the worker's max lifetime shorter than the session TTL, or wrap calls in a retry that re-authenticates on that specific error.
Instance endpoint must be the org URL, not the login URL. The two config blocks (authentication.endpoint vs rest.endpoint) look similar but serve different purposes — mixing them up yields redirects or 404s that look like auth failures.
Bulk data will be slow. There's no mention of Bulk API support; getBySobjectId is strictly single-record. Anything involving thousands of records should bypass this client entirely.
Debugging tip: log the raw HTTP status and Salesforce error code (errorCode field) on failure — the package's isSuccess() is coarse-grained and will hide whether the issue is auth, permissions, or a field-level validation error.
Extension point: you'll likely want to subclass or decorate SalesforceService to add methods (sobject query with SOQL, bulk operations). Since getBySobjectId shows the pattern, adding a query(string $soql) wrapper is where most teams end up within a week.
How can I help you explore Laravel packages today?