Introducing laravel-salesforce - An eloquent like wrapper for Salesforce SOQL
In my role at Leap Software Solutions, I do a lot of work with customer salesforce instances, specifically building 3rd party applications; whether that be for internal reporting tools, to customer portals, backed by salesforce data.
I've been at Leap for a little over a year now, and there was a reocurring theme with the way we worked with Salesforce API; in our projects (typically Laravel/PHP based), we'd have a singular SalesforceService class, which contained everything from Authentication methods, to bespoke query methods.
More often than not, these service classes were thousands of lines long, full of specific/bespoke methods, and every new use case or feature meant bolting on another method. Don't get me wrong, it worked, but the cost of this was duplicated logic, code bloat, and a slow development velocity.
So I decided to fix that.
I built laravel-salesforce, a lightweight, Laravel specific package that mirrors the style of Laravel's own Eloquent ORM, but for Salesforce's SOQL API. Instead of writing hundreds of bespoke query methods, we can now do something much more similar to what you'd see in any typical Laravel project.
Take the following Laravel Controller method:
public function index()
{
$users = User::all();
return view('users.index', compact('users'));
}
Pretty simple, and this was my objective for laravel-salesforce. Super simple Eloquent calls.
In the current way of thinking, we'd have something akin to:
class SalesforceService {
private function get() {
/**
* Wrapper method to handle HTTP Get requests, incorporating Auth
* headers
* */
}
public function fetchAccounts() {
$query = "SELECT Id, Name FROM Account";
$query = urlencode($query);
return $this->get($query);
}
}
Then in our controller:
public function index()
{
$response = $this->salesforceService->fetchAccounts();
if($response && $response->totalSize > 0)
return view('users.index', ['users' => collect($response->records)]);
return view('users.index', ['users' => null]);
}
It's pretty clear - it's messy, lots of extra code, and a long distance away from what we want in an ideal world right?
The laravel-salesforce package completely eliminates that.
public function index()
{
$users = Account::fields(['Id', 'Email', 'Name__c',])
->all()
->records();
return view('users.index', ['users' => $users]);
}
So, there's naturally a couple of differences compared to Eloquent's ORM, but they're for a good reason, I promise.
Firstly, fields(). This was added for 2 reasons. The first was network constraints; if you're selecting a large data set, say 2000+ objects, the last thing you want to do is load 200 redundant fields from an object's schema. Secondly, the alternative was to use the FIELDS(ALL) selector, which has the same network considerations, but additionally, Salesforce imposes a limit on how many records can be queried at a time, resulting in more network requests, and increased latency.
Secondly, records(). An SOQL query request returns not only the content, but also an indicator of totalSize - something that may be useful to the developer.
What's next?
One of the current limitations of laravel-salesforce is the uploading of content documents. Currently you must instantiate dedicated objects, and manually attach to entities; a bit of a tiresome process for the lazy developer (me), so I'll need to add a dedicated static method uploadFile or something similar, to allow for the uploading and attaching of a file directly to an object.
For me, this project was about more than just reducing boilerplate. It was about developer velocity and enjoyment. Writing integrations shouldn’t feel like wrestling with a giant codebase. Now, when I need to grab Salesforce data, it feels natural; and that makes a real difference day-to-day.
It’s still early, but I’m hoping this helps other teams who find themselves in the same position we were. If you’ve ever stared at a bloated service class and thought “there has to be a better way,” this might be it.