daikazu/eloquent-salesforce-objects
Laravel Eloquent-style models for Salesforce sObjects. Query and sync Salesforce data using familiar Eloquent patterns, with object mapping and relationships to work with Accounts, Contacts, and more from your Laravel app.
AuthenticationManager — fixes a production failure mode where concurrent workers all hammered the Salesforce OAuth endpoint after a token-cache miss, triggering 400 unknown_error / retry your request responses from Salesforce.
Cache::lock(...) and read the freshly-cached token via a double-check, instead of each issuing an independent POST /services/oauth2/token.unknown_error / retry your request envelope, HTTP 5xx, and Guzzle ConnectException) are retried with exponential backoff plus jitter. Permanent failures (invalid_grant, credential/config errors, other 4xx) fail fast and are not retried.sha1(consumer_key) so multi-org setups don't collide on a shared lock.LockTimeoutException surfaces as AuthenticationException with a clear message.New authentication block in config/eloquent-salesforce-objects.php. All keys are env-tunable. Existing installations work without changes — defaults are conservative.
| Key | Default | Env var |
|---|---|---|
authentication.retry_attempts |
3 |
SALESFORCE_AUTH_RETRY_ATTEMPTS |
authentication.retry_base_delay_ms |
250 |
SALESFORCE_AUTH_RETRY_BASE_DELAY_MS |
authentication.lock_wait_seconds |
8 |
SALESFORCE_AUTH_LOCK_WAIT_SECONDS |
authentication.lock_ttl_seconds |
10 |
SALESFORCE_AUTH_LOCK_TTL_SECONDS |
AuthenticationManager is unchanged.php artisan vendor:publish --tag="eloquent-salesforce-objects-config" --force
Forrest::hasToken() is now called twice (outer fast-path + double-check inside the lock). Tests that mocked hasToken() with ->once() for the cache-miss path need to be updated to ->twice(). The hot path (token already cached) is unchanged: still a single hasToken() call.describe(), picklistValues(), and related metadata calls on models whose object name matches a registered global alias (e.g. Event, Task, User, Note, Case) were throwing "Class must extend SalesforceModel". resolveObjectName() now only treats a string as a class when it contains a namespace separator, so bare SF object names are passed through to the API as intended.apexRest() no longer strips trailing slashes from paths. Salesforce treats /CreateOrder and /CreateOrder/ as different endpoints, so the path is now preserved as provided. Only a leading slash is added if missing.describe(), picklistValues(), and fieldMetadata() on HasSalesforceMetadata trait callable statically (e.g. Account::describe())getPicklistValues() method — correct method is picklistValues()The first stable release of Eloquent Salesforce Objects — a Laravel package that lets you work with Salesforce objects using familiar Eloquent syntax.
SalesforceModel base classhasMany, belongsTo, and hasOne with Salesforce PascalCase foreign key conventionswhere, whereIn, whereNull, whereBetween, orderBy, limit, offset, scopes, and moreSalesforceBatchcount(), sum(), avg(), min(), max(), exists()paginate() and simplePaginate() with Laravel's built-in paginatorcursor() and automatic nextRecordsUrl handlingwithTrashed() and onlyTrashed() via Salesforce's queryAll and IsDeleted flagapexRest() supporting all HTTP methodsphp artisan make:salesforce-model scaffolds models from live Salesforce metadata with relationships, casts, and default columns$defaultColumns on models; override with allColumns() or explicit select()TODAY, LAST_N_DAYS:30, etc.)php artisan salesforce:test verifies your Salesforce connectionHeavily inspired by roblesterjr04/EloquentSalesForce, rewritten from the ground up with modern PHP 8.4+, full type safety, and a cleaner architecture.
How can I help you explore Laravel packages today?