omniphx/forrest
Laravel package for authenticating with Salesforce via OAuth and calling the REST, SOAP, and Apex APIs. Provides a clean wrapper around Guzzle with token refresh, query helpers, and configuration for seamless Salesforce integrations in Laravel apps.
Installation
composer require omniphx/forrest
php artisan vendor:publish --provider="Omniphx\Forrest\ForrestServiceProvider" --tag="forrest-config"
config/forrest.php) with default Salesforce credentials (client ID, secret, redirect URI, etc.).php artisan forrest:install to generate .env keys if missing.Configuration
.env with Salesforce credentials:
FORREST_CLIENT_ID=your_consumer_key
FORREST_CLIENT_SECRET=your_consumer_secret
FORREST_REDIRECT_URI=http://your-app.test/salesforce/callback
FORREST_LOGIN_URL=https://login.salesforce.com # or sandbox
config/forrest.php:
'scopes' => ['api', 'refresh_token', 'web'], // Required scopes
'default' => 'main', // Default connection name
'connections' => [
'main' => [
'client_id' => env('FORREST_CLIENT_ID'),
'client_secret' => env('FORREST_CLIENT_SECRET'),
'redirect_uri' => env('FORREST_REDIRECT_URI'),
'login_url' => env('FORREST_LOGIN_URL'),
],
],
First Use Case: OAuth Authentication
use Omniphx\Forrest\Facades\Forrest;
return Forrest::auth()->redirect();
Route::get('/salesforce/callback', function () {
$user = Forrest::auth()->user(); // Hydrated Salesforce user
auth()->loginUsingId($user->id); // Optional: Sync with Laravel auth
return redirect('/dashboard');
});
User Synchronization
$salesforceUser = Forrest::auth()->user();
$laravelUser = User::updateOrCreate(
['email' => $salesforceUser->email],
[
'name' => $salesforceUser->name,
'salesforce_id' => $salesforceUser->id,
]
);
SOQL Queries
$accounts = Forrest::query()
->from('Account')
->where('Name', 'like', '%Acme%')
->limit(10)
->get();
$query = Forrest::query()->from('Opportunity');
$query->where('StageName', '=', 'Closed Won');
$query->select(['Id', 'Name', 'Amount']);
$results = $query->get();
Bulk API Operations
$data = [
['Name' => 'Test Account 1', 'Type' => 'Customer'],
['Name' => 'Test Account 2', 'Type' => 'Partner'],
];
Forrest::bulk()->insert('Account', $data);
Webhooks and Real-Time Events
Forrest::events()->subscribe('AccountChangeEvent', function ($payload) {
// Handle real-time updates
Log::info('Account changed:', $payload);
});
config/forrest.php:
'events' => [
'channel' => env('FORREST_EVENT_CHANNEL', 'your_channel_name'),
'consumer_key' => env('FORREST_EVENT_CONSUMER_KEY'),
],
Metadata API
// Retrieve a component (e.g., Apex class)
$metadata = Forrest::metadata()->retrieve('ApexClass', ['MyClass.cls']);
// Deploy changes
Forrest::metadata()->deploy($metadata);
Laravel Auth Integration
Use Forrest::auth()->user() to hydrate a Laravel User model and leverage Laravel’s auth system for role-based access control (RBAC).
Caching Responses Cache SOQL results or metadata to reduce API calls:
$accounts = Cache::remember('salesforce_accounts', now()->addHours(1), function () {
return Forrest::query()->from('Account')->get();
});
Error Handling
Wrap Forrest calls in try-catch blocks to handle Salesforce-specific errors (e.g., AuthException, QueryException):
try {
$user = Forrest::auth()->user();
} catch (\Omniphx\Forrest\Exceptions\AuthException $e) {
abort(403, 'Salesforce authentication failed');
}
Testing
Use the ForrestFake service provider for unit tests:
use Omniphx\Forrest\Testing\ForrestFake;
beforeEach(function () {
ForrestFake::fake();
});
it('fetches a user', function () {
ForrestFake::shouldReceive('user')->andReturn(new SalesforceUser());
$user = Forrest::auth()->user();
// Assertions...
});
OAuth Token Expiry
Forrest::auth()->refreshTokenIfExpired();
SOQL Injection
// UNSAFE: Direct SOQL string
$results = Forrest::query()->from("Account WHERE Name = '$unsafeInput'");
// SAFE: Query builder
$results = Forrest::query()->from('Account')->where('Name', '=', $safeInput)->get();
Rate Limiting
try {
Forrest::query()->from('Account')->get();
} catch (\Omniphx\Forrest\Exceptions\RateLimitException $e) {
sleep($e->retryAfter);
retry();
}
Connection Management
Forrest::connection('sandbox')->query()->from('Account')->get();
config/forrest.php defines all connections.Platform Event Delays
replayId to track processed events and avoid duplicates.Enable Logging
Add to config/forrest.php:
'logging' => true,
Logs will appear in storage/logs/laravel.log.
Inspect Raw Responses
Use Forrest::getLastResponse() to debug API calls:
$response = Forrest::query()->from('Account')->get();
\Log::info(Forrest::getLastResponse()->getBody());
Common Errors
InvalidClientId: Verify FORREST_CLIENT_ID and FORREST_CLIENT_SECRET in .env.InsufficientAccess: Check OAuth scopes in config/forrest.php.MalformedQuery: Validate SOQL syntax using the Salesforce SOQL Explorer.Custom Fields and Objects
Extend the SalesforceUser model or create custom hydrators:
Forrest::extend('user', function ($user) {
$user->customField = Forrest::query()
->from('CustomObject__c')
->where('User__c', '=', $user->id)
->first();
return $user;
});
Custom API Endpoints
Use the underlying Guzzle client for non-SOQL endpoints:
$client = Forrest::connection()->getClient();
$response = $client->get('services/data/v56.0/sobjects/Account');
Event Listeners
Listen for Forrest events (e.g., auth.failed, query.executed):
Forrest::listen('auth.failed', function ($exception) {
\Log::warning('Salesforce auth failed', ['error' => $exception->getMessage()]);
});
Testing Helpers
Extend ForrestFake for custom test data:
ForrestFake::fake([
How can I help you explore Laravel packages today?