{"id":42861300,"url":"https://github.com/codelicia/trineforce","last_synced_at":"2026-01-30T12:37:49.356Z","repository":{"id":35119319,"uuid":"203819080","full_name":"codelicia/trineforce","owner":"codelicia","description":"A nice ✨ gambiarra ✨ to work with Salesforce SOQL Queries and Doctrine DBAL","archived":false,"fork":false,"pushed_at":"2025-12-25T17:56:17.000Z","size":283,"stargazers_count":5,"open_issues_count":12,"forks_count":6,"subscribers_count":2,"default_branch":"2.x.x","last_synced_at":"2025-12-27T04:44:08.312Z","etag":null,"topics":["doctrine","doctrine-dbal","salesforce","salesforce-developers"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codelicia.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["malukenho","EHER"]}},"created_at":"2019-08-22T15:08:00.000Z","updated_at":"2025-08-25T16:48:18.000Z","dependencies_parsed_at":"2024-04-02T07:42:25.134Z","dependency_job_id":"efe3c35d-15d7-46b1-9b63-79139da49dbe","html_url":"https://github.com/codelicia/trineforce","commit_stats":{"total_commits":146,"total_committers":9,"mean_commits":16.22222222222222,"dds":0.2808219178082192,"last_synced_commit":"0641fd59272f12cb80877d8c33e4dd478c8cd8b4"},"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/codelicia/trineforce","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codelicia%2Ftrineforce","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codelicia%2Ftrineforce/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codelicia%2Ftrineforce/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codelicia%2Ftrineforce/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codelicia","download_url":"https://codeload.github.com/codelicia/trineforce/tar.gz/refs/heads/2.x.x","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codelicia%2Ftrineforce/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28912913,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-30T12:13:43.263Z","status":"ssl_error","status_checked_at":"2026-01-30T12:13:22.389Z","response_time":66,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["doctrine","doctrine-dbal","salesforce","salesforce-developers"],"created_at":"2026-01-30T12:37:48.606Z","updated_at":"2026-01-30T12:37:49.349Z","avatar_url":"https://github.com/codelicia.png","language":"PHP","funding_links":["https://github.com/sponsors/malukenho","https://github.com/sponsors/EHER"],"categories":[],"sub_categories":[],"readme":"SOQL Doctrine DBAL\n==================\n\n**Salesforce Soql Doctrine Driver** allows you to write Soql queries\nand interact with a Salesforce instance using the Doctrine DBAL layer.\n\nNow one can forget about Salesforce and have a nice `repository/query object`\nintegration on one's architecture without hurting that much on the usual\nproject structure.\n\n### Installation\n\nUse `composer` to install this package as bellow:\n\n```shell script\n$ composer require codelicia/trineforce\n```\n\n### Configuration\n\nIf you are familiar with Doctrine, then you probably already know how to\nconfigure and use it. But some special configuration is required in order\nto make it work.\n\nWhen creating a new `Connection`, you should also provide the configuration\nkeys for `salesforceInstance`, `consumerKey`, `consumerSecret` and point to\nthe right `driverClass`. The usual `user` and `password` are also required.\n\n```php\n$config = new Configuration();\n$connectionParams = [\n    'salesforceInstance' =\u003e 'https://[SALESFORCE INSTANCE].salesforce.com',\n    'apiVersion'         =\u003e 'v43.0',\n    'user'               =\u003e 'salesforce-user@email.com',\n    'password'           =\u003e 'salesforce-password',\n    'consumerKey'        =\u003e '...',\n    'consumerSecret'     =\u003e '...',\n    'driverClass'        =\u003e \\Codelicia\\Soql\\SoqlDriver::class,\n    'wrapperClass'       =\u003e \\Codelicia\\Soql\\ConnectionWrapper::class,\n];\n\n/** @var \\Codelicia\\Soql\\ConnectionWrapper $conn */\n$conn = DriverManager::getConnection($connectionParams, $config);\n```\n\n* `user` provides the login, which is usually an email to access the salesforce\n  instance.\n* `password` provides the corresponding password to the email provided on `user`.\n* `salesforceInstance` points to the url of the Salesforce instance.\n* `apiVersion` specify a salesforce API version to work with.\n* `consumerKey` provides the integration consumer key\n* `consumerSecret` provides the integration consumer secret\n* `driverClass` should points to `\\Codelicia\\Soql\\SoqlDriver::class`\n* `wrapperClass` should points to `\\Codelicia\\Soql\\ConnectionWrapper::class`\n\nBy setting up the `wrapperClass`, we can make use of a proper `QueryBuild` that allow\n`JOIN` in the Salesforce format.\n\nWhen using the doctrine bundle and the dbal is configured through yaml the options should\nbe passed in a different way for the validation that the bundle does.\n\n```yaml\ndoctrine:\n    dbal:\n        driver: soql\n        user: '%env(resolve:SALESFORCE_USERNAME)%'\n        password: '%env(resolve:SALESFORCE_PASSWORD)%'\n        driver_class: '\\Codelicia\\Soql\\SoqlDriver'\n        wrapper_class: '\\Codelicia\\Soql\\ConnectionWrapper'\n        options:\n            salesforceInstance: '%env(resolve:SALESFORCE_ENDPOINT)%'\n            apiVersion: v56.0\n            consumerKey: '%env(resolve:SALESFORCE_CLIENT_ID)%'\n            consumerSecret: '%env(resolve:SALESFORCE_CLIENT_SECRET)%'\n```\n\n### Using DBAL\n\nNow that you have the connection set up, you can use Doctrine `QueryBuilder` to\nquery some data as bellow:\n\n```php\n$id = '0062X00000vLZDVQA4';\n\n$sql = $conn-\u003ecreateQueryBuilder()\n    -\u003eselect(['Id', 'Name', 'Status__c'])\n    -\u003efrom('Opportunity')\n    -\u003ewhere('Id = :id')\n    -\u003eandWhere('Name = :name')\n    -\u003esetParameter('name', 'Pay as you go Opportunity')\n    -\u003esetParameter('id', $id)\n    -\u003esetMaxResults(1)\n    -\u003eexecute();\n\nvar_dump($sql-\u003efetchAll()); // All rest api result\n```\n\nor use the normal `Connection#query()` method.\n\n### Basic Operations\n\nHere are some examples of basic `CRUD` operations.\n\n#### `Connection#insert()`\n\nCreating an `Account` with the `Name` of `John`:\n```php\n$connection-\u003einsert('Account', ['Name' =\u003e 'John']);\n```\n\n#### `Connection#delete()`\n\nDeleting an `Account` with the `Id` = `1234`:\n```php\n$connection-\u003edelete('Account', ['Id' =\u003e '1234']);\n```\n\n#### `Connection#update()`\n\nUpdate an `Account` with the `Name` of `Sr. John` where the `Id` is `1234`:\n```php\n$connection-\u003eupdate('Account', ['Name' =\u003e 'Sr. John'], ['Id' =\u003e '1234']);\n```\n\n### Be Transactional with `Composite` API\n\nAs salesforce released the `composite` api, it gave us the ability\nto simulate transactions as in a database. So, we can use the same\nDoctrine DBAL api that you already know to do transactional operations\nin your Salesforce instance.\n\n```php\n$conn-\u003ebeginTransaction();\n\n$conn-\u003einsert('Account', ['Name' =\u003e 'John']);\n$conn-\u003einsert('Account', ['Name' =\u003e 'Elsa']);\n\n$conn-\u003ecommit();\n```\n\nOr even, use the `Connection#transactional()` helper, as you prefer.\n\n#### Referencing another Records\n\nThe `composite` api, also enables us to compose a structure data to be\nchanged in one single request. So we can cross reference records as it\nfits our needs.\n\nLet's see how to create an `Account` and a linked `Contact` to that `Account`\nin a single `composite` request.\n\n```php\n$conn-\u003etransactional(static function () use ($conn) {\n\n    $conn-\u003einsert('Account', ['Name' =\u003e 'John'], ['referenceId' =\u003e 'account']);\n    $conn-\u003einsert('Contact', [\n        'FirstName' =\u003e 'John',\n        'LastName' =\u003e 'Contact',\n        'AccountId' =\u003e '@{account.id}' // reference `Account` by its `referenceId`\n    ]);\n\n});\n```\n\n### 🚫 Known Limitations\n\nAs of today, we cannot consume a `sObject` using the `queryBuilder` to get all fields from\nthe `sObject`. That is because Salesforce doesn't accept `SELECT *` as a valid query.\n\nThe workaround that issue is to do a `GET` request to specific resources, then can grab all\ndata related to that resource.\n\n```php\n$this-\u003econnection\n    -\u003egetNativeConnection() // : \\GuzzleHttp\\ClientInterface\n    -\u003erequest(\n        'GET',\n        sprintf('/services/data/v40.0/sobjects/Opportunity/%s', $id)\n    )\n    -\u003egetBody()\n    -\u003egetContents()\n;\n```\n\n### 📈 Diagram\n\n```mermaid\n%%{init: {'sequence': { 'mirrorActors': false, 'rightAngles': true, 'messageAlign': 'center', 'actorFontSize': 20, 'actorFontWeight': 900, 'noteFontSize': 18, 'noteFontWeight': 600, 'messageFontSize': 20}}}%%\n%%{init: {'theme': 'base', 'themeVariables': { 'actorBorder': '#D86613', 'activationBorderColor': '#232F3E', 'activationBkgColor': '#D86613','noteBorderColor': '#232F3E', 'signalColor': 'white', 'signalTextColor': 'gray', 'sequenceNumberColor': '#232F3E'}}}%%\nsequenceDiagram\n    autonumber\n    Note left of ConnectionWrapper: Everything starts with \u003cbr/\u003ethe ConnectionWrapper.\n    ConnectionWrapper-\u003e\u003eQueryBuilder: createQueryBuilder()\n    activate QueryBuilder\n    alt \n        QueryBuilder-\u003e\u003eQueryBuilder: execute() \u003cbr\u003eCalls private executeQuery()\u003cbr\u003emethod\n    end\n    QueryBuilder-\u003e\u003e+ConnectionWrapper: executeQuery()\n    deactivate QueryBuilder\n    ConnectionWrapper-\u003e\u003eSoqlStatement: execute() \n    SoqlStatement-\u003e\u003e+\\Doctrine\\DBAL\\Driver\\Result: execute()\n    ConnectionWrapper-\u003e\u003e+\\Codelicia\\Soql\\DBAL\\Result: new\n    \\Doctrine\\DBAL\\Driver\\Result--\u003e\u003e\\Codelicia\\Soql\\DBAL\\Result: pass to\n    \\Codelicia\\Soql\\DBAL\\Result--\u003e\u003e-ConnectionWrapper: returns\n    ConnectionWrapper-\u003e\u003e-SoqlStatement: fetchAll()\n    SoqlStatement-\u003e\u003e+\\Codelicia\\Soql\\FetchDataUtility: fetchAll()\n    \\Codelicia\\Soql\\FetchDataUtility--\u003e\u003e+\\GuzzleHttp\\ClientInterface: send()\n    Note right of \\Codelicia\\Soql\\FetchDataUtility: Countable goes here?\u003cbr\u003e before creating the Payload?\n    \\Codelicia\\Soql\\FetchDataUtility-\u003e\u003e+\\Codelicia\\Soql\\Payload: new\n    \\Codelicia\\Soql\\Payload--\u003e\u003e+SoqlStatement: returns\n```\n\n### Author\n\n- Jefersson Nathan ([@malukenho](http://github.com/malukenho))\n- Alexandre Eher ([@Eher](http://github.com/EHER))\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodelicia%2Ftrineforce","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodelicia%2Ftrineforce","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodelicia%2Ftrineforce/lists"}