PHPackages                             oilstone/api-salesforce-integration - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. oilstone/api-salesforce-integration

ActiveLibrary[API Development](/categories/api)

oilstone/api-salesforce-integration
===================================

A Salesforce integration package for garethhudson07/api

0380PHPCI passing

Since Jun 26Pushed 1mo agoCompare

[ Source](https://github.com/oilstone/api-salesforce-integration)[ Packagist](https://packagist.org/packages/oilstone/api-salesforce-integration)[ RSS](/packages/oilstone-api-salesforce-integration/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersions (4)Used By (0)

API Salesforce Integration
==========================

[](#api-salesforce-integration)

A lightweight integration for interacting with Salesforce from PHP. The package supplies a stand‑alone client and query builder while also providing adapters for the [garethhudson07/api](https://github.com/garethhudson07/api) framework.

Features
--------

[](#features)

- **Salesforce HTTP client** built on Guzzle with convenience helpers for common endpoints.
- **Fluent SOQL query builder** supporting nested conditions, `IN` clauses, ordering, limits and relationship includes.
- **Repository layer** exposing `find`, `first`, `get`, `create`, `update`, `upsert`, `upsertRecord` and `delete` methods for Salesforce objects.
- **Integration with garethhudson07/api** through repository and query bridge classes and a data transformer so that resources defined in that package can query Salesforce seamlessly.
- **Laravel support** including a service provider for obtaining and caching OAuth tokens and optional request logging.
- **Lookup utilities** for retrieving and caching pick list values.
- **Adapters for api-resource-loader** allowing resources to be loaded from configuration files.

Although the package was designed to act as a bridge for `garethhudson07/api`, the client, query builder and repository classes can be used independently in any PHP project.

Installation
------------

[](#installation)

```
composer require oilstone/api-salesforce-integration
```

### Optional Laravel setup

[](#optional-laravel-setup)

If your project uses Laravel you can register the service provider and publish the configuration file:

```
// config/app.php
'providers' => [
    \Oilstone\ApiSalesforceIntegration\Integrations\Laravel\ServiceProvider::class,
],
```

```
php artisan vendor:publish --tag=config --provider="Oilstone\\ApiSalesforceIntegration\\Integrations\\Laravel\\ServiceProvider"
```

Configure your Salesforce instance in `config/salesforce.php` and the provider will handle authentication and caching of access tokens. When the `debug` option is enabled each request and response is logged via Laravel's logger. Queries served from the cache are also logged with a `cache` flag so they can be distinguished from live requests.

OAuth access tokens are managed by `SalesforceTokenManager`, which caches the token under the key `salesforce.access_token`, derives its TTL from the `expires_in` field returned by Salesforce (with a 60 second safety margin), serialises concurrent token fetches with a cache lock to avoid thundering-herd requests, and transparently refreshes the token if any Salesforce call returns HTTP 401. This works out of the box with the Redis cache store.

When authenticating with the client credentials grant you must supply at least one OAuth scope. Set the `SALESFORCE_SCOPES` environment variable to a comma separated list (or define the `scopes` array in the published configuration) and the service provider will include them in the token request.

The package divides caching into three independent layers via `QueryCacheHandler`:

- **Query cache** entries persist the results of SOQL queries for a short TTL. The SOQL string itself is hashed to generate the cache key and a single `flushQueryCache` method invalidates every cached query in one call by rotating an internal namespace token. The query cache is also flushed automatically by any repository mutation (`create`, `update`, `upsert`, `upsertRecord`, `delete`).
- **Entry cache** entries store individual records returned from `find` / `first` lookups for a longer TTL. Each cached entry is indexed under every configured "indexable field" (the record's `Id` by default, plus any extra unique columns you register via `setIndexableFields`). Mutations invalidate the entry under each known indexable field so a record cached by `Email`remains in sync after an update keyed by `Id`. Negative results (`null` for "not found") are deliberately not cached, so a subsequent `create` is never fooled by a stale miss.
- **Schema cache** entries store object describe payloads and picklist value responses, which rarely change. Schema entries live in their own namespace and are **not** affected by `flushQueryCache`, so the (expensive) describe call is preserved across data mutations. Use `flushSchemaCache` or the `--schema` flag on the Artisan command to invalidate them after a Salesforce metadata change.

### Configuring entry cache invalidation

[](#configuring-entry-cache-invalidation)

By default the entry cache is keyed by the repository's identifier (`Id`, or whatever you set with `setIdentifier`). If you also look records up by other unique fields (an external ID, an email column, etc.), register them so mutations can invalidate every cached copy:

```
$repository = (new Repository('Contact'))
    ->setIdentifier('Id')
    ->setIndexableFields(['Id', 'Email', 'External_Id__c']);
```

Only register fields that uniquely identify a record. Non-unique fields (e.g. `Status`) are not appropriate as indexable fields. Note that if the value of an indexable field changes during an update, the cache entry keyed under the old value is left to expire via TTL — this is rarely a problem when indexable fields are immutable identifiers.

### Clearing caches manually

[](#clearing-caches-manually)

```
php artisan salesforce:cache:clear                                          # Flushes all query cache entries
php artisan salesforce:cache:clear Account 001XXXXXXXXXXXXXXX               # Also forgets the entry cache for that Id
php artisan salesforce:cache:clear Account 001XXXXXXXXXXXXXXX --field=External_Id__c
php artisan salesforce:cache:clear --schema                                 # Flushes only the schema cache
```

### Configuration

[](#configuration)

Default TTLs and behaviour can be configured via environment variables (or the corresponding keys in the published `salesforce.php` configuration file):

VariableDefaultPurpose`SALESFORCE_QUERY_CACHE_DEFAULT_TTL``3600`TTL (seconds) for cached SOQL query results.`SALESFORCE_ENTRY_CACHE_DEFAULT_TTL``86400`TTL (seconds) for cached individual records.`SALESFORCE_SCHEMA_CACHE_DEFAULT_TTL``86400`TTL (seconds) for cached describe / picklist payloads.`SALESFORCE_SKIP_RETRIEVAL_DEFAULT``false`When `true`, every cached lookup bypasses the cache on the way in (cache is still populated). Useful for long-running queue workers that need fresh reads.You can also opt out of the cache on a single call by passing `'skip_retrieval' => true` in the repository options array.

Basic usage
-----------

[](#basic-usage)

### Stand‑alone

[](#standalone)

```
use GuzzleHttp\Client;
use Oilstone\ApiSalesforceIntegration\Clients\Salesforce;
use Oilstone\ApiSalesforceIntegration\Repository;

$http = new Client();
$salesforce = new Salesforce($http, $instanceUrl, $accessToken);
$accounts = (new Repository('Account'))
    ->setClient($salesforce)
    ->setDefaultConstraints([['Type', 'Customer']])
    ->newQuery()
    ->where('Name', 'like', 'Acme%')
    ->get();
```

### With garethhudson07/api

[](#with-garethhudson07api)

Create a resource repository that extends the provided API adapter and let the framework resolve queries against Salesforce:

```
use Oilstone\ApiSalesforceIntegration\Integrations\Api\Repository as ApiRepository;

class AccountRepository extends ApiRepository
{
    protected string $object = 'Account';
    // Optionally customise the identifier column
    protected string $identifier = 'External_Id__c';
}
```

The package's query resolver and transformer bridge the API pipeline to Salesforce so existing endpoints defined in `garethhudson07/api` continue to work with Salesforce data.

### Including related records

[](#including-related-records)

Use the `with` method when building a query to fetch related records. Pass the child object name (or relationship name) to include the `Id` and `Name` fields for that relationship by default:

```
$account = (new Repository('Account'))
    ->setClient($salesforce)
    ->newQuery()
    ->with('Museum_Facility__c')
    ->first();
```

You can target specific fields on the related object using a colon syntax:

```
$account = (new Repository('Account'))
    ->setClient($salesforce)
    ->newQuery()
    ->with('Contacts:FirstName,LastName')
    ->first();
```

Related data is returned as a simple array of child records without the Salesforce metadata wrappers.

### Creating a fresh repository

[](#creating-a-fresh-repository)

When you need a repository for a different object without inheriting the current repository's default constraints, includes or schema defaults, use `freshRepository`:

```
$accountRepo = (new AccountRepository())->setClient($salesforce);
$facilityRepo = $accountRepo->freshRepository('Museum_Facility__c');
```

The returned repository is clean and can be configured independently.

Apex REST requests
------------------

[](#apex-rest-requests)

Some Salesforce integrations expose Apex REST endpoints under `/services/apexrest`. Use the `apexRest` helper on the client to call these resources directly:

```
use GuzzleHttp\Client;
use Oilstone\ApiSalesforceIntegration\Clients\Salesforce;

$http = new Client();
$salesforce = new Salesforce($http, $instanceUrl, $accessToken);

$paymentMethods = $salesforce->apexRest('GET', 'cpm/v2/PaymentMethods');
```

Schema meta properties
----------------------

[](#schema-meta-properties)

When a resource is backed by an `Api\Schema\Schema`, meta properties on the schema fields control how values are fetched from Salesforce, transformed for API consumers and written back. The integration recognises the following meta keys:

Meta keyBehaviour`validationOnly`Excludes the field from every read/write operation so it can still be validated by the API schema without hitting Salesforce.`needs`Ensures additional Salesforce fields are always selected with the property (accepts a string or array of field names).`calculated`Marks the property as derived so it is never selected, has no defaults extracted and is ignored when writing back.`isRelation`Skips the property when building field lists and payloads because the value comes from relationship includes.`readonly`Omits the property from create/update payloads unless `forceReverse` is used on the transformer.`fixed`Forces the property to a constant value for reads and writes, and seeds that value when building defaults.`default`Provides a fallback when no explicit value is supplied and is also surfaced by `Repository::getDefaultValues()`.`beforeTransform` / `afterTransform`Callables that run before/after a record is transformed for API output, letting you massage inbound values.`beforeReverse` / `afterReverse`Callables that run before/after values are prepared for Salesforce, giving hooks for last-mile tweaks.`delimited`Treats the field as a delimited list. Responses are exploded into arrays and outbound arrays are imploded using the delimiter string stored on the property.`isYesNo`Converts 'Yes'/'No' Salesforce strings to booleans when reading, back to Salesforce-friendly strings when writing, and applies the same mapping to query constraints.`isAddressLine`Maps a specific numbered line of a multi-line address field when transforming in either direction, rebuilding the combined field on writes.These meta keys can be combined to tailor how each schema property interacts with Salesforce while keeping the resource definition declarative.

Resource-level transform callbacks
----------------------------------

[](#resource-level-transform-callbacks)

For cross-field shaping that should happen once around the whole schema transformation, register callbacks on the resource itself:

```
class AccountResource extends \Oilstone\ApiSalesforceIntegration\Integrations\ApiResourceLoader\Resource
{
    public function __construct()
    {
        parent::__construct();

        $this->beforeTransform(function (array $attributes) {
            $attributes['FullName'] = trim(($attributes['FirstName'] ?? '') . ' ' . ($attributes['LastName'] ?? ''));

            return $attributes;
        });

        $this->afterTransform(function (array $attributes) {
            $attributes['display_name'] = strtoupper($attributes['full_name'] ?? '');

            return $attributes;
        });
    }
}
```

`beforeTransform()` runs before schema mapping and receives the raw Salesforce attributes. `afterTransform()` runs after schema mapping and receives the transformed attributes. Both callbacks may also accept the current record and schema as later arguments when needed.

Resource-level cache toggle
---------------------------

[](#resource-level-cache-toggle)

Salesforce resource classes cache query results by default. To disable caching for a single resource, set `cacheEnabled` to `false` (or call `setCacheEnabled(false)`):

```
class LiveAccountResource extends \Oilstone\ApiSalesforceIntegration\Integrations\ApiResourceLoader\Resource
{
    protected bool $cacheEnabled = false;
}
```

This applies only to repositories created through that resource; other resources continue using cache by default.

Lookups
-------

[](#lookups)

Extend `Lookup` or `CachedLookup` to pull picklist values from Salesforce:

```
class IndustryLookup extends CachedLookup
{
    public static function object(): string { return 'Account'; }
    public static function field(): string { return 'Industry'; }
    public static function recordTypeId(): string { return '0123...'; }
}

$industries = IndustryLookup::all();
```

Exceptions
----------

[](#exceptions)

`SalesforceException` is thrown for non‑successful responses and provides access to the underlying error details via `getErrors()`.

License
-------

[](#license)

This package is released under the MIT license.

###  Health Score

26

—

LowBetter than 41% of packages

Maintenance61

Regular maintenance activity

Popularity17

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity15

Early-stage or recently created project

 Bus Factor1

Top contributor holds 99.6% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/30072434?v=4)[oilstone](/maintainers/oilstone)[@oilstone](https://github.com/oilstone)

---

Top Contributors

[![bullenb](https://avatars.githubusercontent.com/u/66941?v=4)](https://github.com/bullenb "bullenb (265 commits)")[![oilstone](https://avatars.githubusercontent.com/u/30072434?v=4)](https://github.com/oilstone "oilstone (1 commits)")

### Embed Badge

![Health badge](/badges/oilstone-api-salesforce-integration/health.svg)

```
[![Health](https://phpackages.com/badges/oilstone-api-salesforce-integration/health.svg)](https://phpackages.com/packages/oilstone-api-salesforce-integration)
```

###  Alternatives

[exsyst/swagger

A php library to manipulate Swagger specifications

35916.4M7](/packages/exsyst-swagger)[hubspot/api-client

Hubspot API client

24016.2M20](/packages/hubspot-api-client)[pocketmine/bedrock-protocol

An implementation of the Minecraft: Bedrock Edition protocol in PHP

172445.0k14](/packages/pocketmine-bedrock-protocol)[botman/driver-telegram

Telegram driver for BotMan

93459.5k6](/packages/botman-driver-telegram)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
