Skip to content

Repository files navigation

SOQL Doctrine DBAL

Salesforce Soql Doctrine Driver allows you to write Soql queries and interact with a Salesforce instance using the Doctrine DBAL layer.

Now one can forget about Salesforce and have a nice repository/query object integration on one's architecture without hurting that much on the usual project structure.

Installation

Use composer to install this package as bellow:

$ composer require codelicia/trineforce

Configuration

If you are familiar with Doctrine, then you probably already know how to configure and use it. But some special configuration is required in order to make it work.

When creating a new Connection, you should also provide the configuration keys for salesforceInstance, consumerKey, consumerSecret and point to the right driverClass. The usual user and password are also required.

$config = newConfiguration();
$connectionParams = [
'salesforceInstance' => 'https://[SALESFORCE INSTANCE].salesforce.com',
'apiVersion' => 'v43.0',
'user' => 'salesforce-user@email.com',
'password' => 'salesforce-password',
'consumerKey' => '...',
'consumerSecret' => '...',
'driverClass' => \Codelicia\Soql\SoqlDriver::class,
'wrapperClass' => \Codelicia\Soql\ConnectionWrapper::class,
];
/** @var \Codelicia\Soql\ConnectionWrapper $conn */$conn = DriverManager::getConnection($connectionParams, $config);
  • user provides the login, which is usually an email to access the salesforce instance.
  • password provides the corresponding password to the email provided on user.
  • salesforceInstance points to the url of the Salesforce instance.
  • apiVersion specify a salesforce API version to work with.
  • consumerKey provides the integration consumer key
  • consumerSecret provides the integration consumer secret
  • driverClass should points to \Codelicia\Soql\SoqlDriver::class
  • wrapperClass should points to \Codelicia\Soql\ConnectionWrapper::class

By setting up the wrapperClass, we can make use of a proper QueryBuild that allow JOIN in the Salesforce format.

When using the doctrine bundle and the dbal is configured through yaml the options should be passed in a different way for the validation that the bundle does.

doctrine:
dbal:
driver: soqluser: '%env(resolve:SALESFORCE_USERNAME)%'password: '%env(resolve:SALESFORCE_PASSWORD)%'driver_class: '\Codelicia\Soql\SoqlDriver'wrapper_class: '\Codelicia\Soql\ConnectionWrapper'options:
salesforceInstance: '%env(resolve:SALESFORCE_ENDPOINT)%'apiVersion: v56.0consumerKey: '%env(resolve:SALESFORCE_CLIENT_ID)%'consumerSecret: '%env(resolve:SALESFORCE_CLIENT_SECRET)%'

Using DBAL

Now that you have the connection set up, you can use Doctrine QueryBuilder to query some data as bellow:

$id = '0062X00000vLZDVQA4';
$sql = $conn->createQueryBuilder()
->select(['Id', 'Name', 'Status__c'])
->from('Opportunity')
->where('Id = :id')
->andWhere('Name = :name')
->setParameter('name', 'Pay as you go Opportunity')
->setParameter('id', $id)
->setMaxResults(1)
->execute();
var_dump($sql->fetchAll()); // All rest api result

or use the normal Connection#query() method.

Basic Operations

Here are some examples of basic CRUD operations.

Connection#insert()

Creating an Account with the Name of John:

$connection->insert('Account', ['Name' => 'John']);

Connection#delete()

Deleting an Account with the Id = 1234:

$connection->delete('Account', ['Id' => '1234']);

Connection#update()

Update an Account with the Name of Sr. John where the Id is 1234:

$connection->update('Account', ['Name' => 'Sr. John'], ['Id' => '1234']);

Be Transactional with Composite API

As salesforce released the composite api, it gave us the ability to simulate transactions as in a database. So, we can use the same Doctrine DBAL api that you already know to do transactional operations in your Salesforce instance.

$conn->beginTransaction();
$conn->insert('Account', ['Name' => 'John']);
$conn->insert('Account', ['Name' => 'Elsa']);
$conn->commit();

Or even, use the Connection#transactional() helper, as you prefer.

Referencing another Records

The composite api, also enables us to compose a structure data to be changed in one single request. So we can cross reference records as it fits our needs.

Let's see how to create an Account and a linked Contact to that Account in a single composite request.

$conn->transactional(staticfunction () use ($conn) {
$conn->insert('Account', ['Name' => 'John'], ['referenceId' => 'account']);
$conn->insert('Contact', [
'FirstName' => 'John',
'LastName' => 'Contact',
'AccountId' => '@{account.id}'// reference `Account` by its `referenceId`
]);
});

🚫 Known Limitations

As of today, we cannot consume a sObject using the queryBuilder to get all fields from the sObject. That is because Salesforce doesn't accept SELECT * as a valid query.

The workaround that issue is to do a GET request to specific resources, then can grab all data related to that resource.

$this->connection
->getNativeConnection() // : \GuzzleHttp\ClientInterface
->request(
'GET',
sprintf('/services/data/v40.0/sobjects/Opportunity/%s', $id)
)
->getBody()
->getContents()
;

📈 Diagram

%%{init: {'sequence': { 'mirrorActors': false, 'rightAngles': true, 'messageAlign': 'center', 'actorFontSize': 20, 'actorFontWeight': 900, 'noteFontSize': 18, 'noteFontWeight': 600, 'messageFontSize': 20}}}%%
%%{init: {'theme': 'base', 'themeVariables': { 'actorBorder': '#D86613', 'activationBorderColor': '#232F3E', 'activationBkgColor': '#D86613','noteBorderColor': '#232F3E', 'signalColor': 'white', 'signalTextColor': 'gray', 'sequenceNumberColor': '#232F3E'}}}%%
sequenceDiagram
autonumber
Note left of ConnectionWrapper: Everything starts with <br/>the ConnectionWrapper.
ConnectionWrapper->>QueryBuilder: createQueryBuilder()
activate QueryBuilder
alt QueryBuilder->>QueryBuilder: execute() <br>Calls private executeQuery()<br>method
end
QueryBuilder->>+ConnectionWrapper: executeQuery()
deactivate QueryBuilder
ConnectionWrapper->>SoqlStatement: execute() SoqlStatement->>+\Doctrine\DBAL\Driver\Result: execute()
ConnectionWrapper->>+\Codelicia\Soql\DBAL\Result: new
\Doctrine\DBAL\Driver\Result-->>\Codelicia\Soql\DBAL\Result: pass to
\Codelicia\Soql\DBAL\Result-->>-ConnectionWrapper: returns
ConnectionWrapper->>-SoqlStatement: fetchAll()
SoqlStatement->>+\Codelicia\Soql\FetchDataUtility: fetchAll()
\Codelicia\Soql\FetchDataUtility-->>+\GuzzleHttp\ClientInterface: send()
Note right of \Codelicia\Soql\FetchDataUtility: Countable goes here?<br> before creating the Payload?
\Codelicia\Soql\FetchDataUtility->>+\Codelicia\Soql\Payload: new
\Codelicia\Soql\Payload-->>+SoqlStatement: returns
Loading

Author

About

A nice ✨ gambiarra ✨ to work with Salesforce SOQL Queries and Doctrine DBAL

Topics

Resources

Stars

5 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - codelicia/trineforce: A nice ✨ gambiarra ✨ to work with Salesforce SOQL Queries and Doctrine DBAL · GitHub
Skip to content

Repository files navigation

SOQL Doctrine DBAL

Salesforce Soql Doctrine Driver allows you to write Soql queries and interact with a Salesforce instance using the Doctrine DBAL layer.

Now one can forget about Salesforce and have a nice repository/query object integration on one's architecture without hurting that much on the usual project structure.

Installation

Use composer to install this package as bellow:

$ composer require codelicia/trineforce

Configuration

If you are familiar with Doctrine, then you probably already know how to configure and use it. But some special configuration is required in order to make it work.

When creating a new Connection, you should also provide the configuration keys for salesforceInstance, consumerKey, consumerSecret and point to the right driverClass. The usual user and password are also required.

$config = newConfiguration();
$connectionParams = [
'salesforceInstance' => 'https://[SALESFORCE INSTANCE].salesforce.com',
'apiVersion' => 'v43.0',
'user' => 'salesforce-user@email.com',
'password' => 'salesforce-password',
'consumerKey' => '...',
'consumerSecret' => '...',
'driverClass' => \Codelicia\Soql\SoqlDriver::class,
'wrapperClass' => \Codelicia\Soql\ConnectionWrapper::class,
];
/** @var \Codelicia\Soql\ConnectionWrapper $conn */$conn = DriverManager::getConnection($connectionParams, $config);
  • user provides the login, which is usually an email to access the salesforce instance.
  • password provides the corresponding password to the email provided on user.
  • salesforceInstance points to the url of the Salesforce instance.
  • apiVersion specify a salesforce API version to work with.
  • consumerKey provides the integration consumer key
  • consumerSecret provides the integration consumer secret
  • driverClass should points to \Codelicia\Soql\SoqlDriver::class
  • wrapperClass should points to \Codelicia\Soql\ConnectionWrapper::class

By setting up the wrapperClass, we can make use of a proper QueryBuild that allow JOIN in the Salesforce format.

When using the doctrine bundle and the dbal is configured through yaml the options should be passed in a different way for the validation that the bundle does.

doctrine:
dbal:
driver: soqluser: '%env(resolve:SALESFORCE_USERNAME)%'password: '%env(resolve:SALESFORCE_PASSWORD)%'driver_class: '\Codelicia\Soql\SoqlDriver'wrapper_class: '\Codelicia\Soql\ConnectionWrapper'options:
salesforceInstance: '%env(resolve:SALESFORCE_ENDPOINT)%'apiVersion: v56.0consumerKey: '%env(resolve:SALESFORCE_CLIENT_ID)%'consumerSecret: '%env(resolve:SALESFORCE_CLIENT_SECRET)%'

Using DBAL

Now that you have the connection set up, you can use Doctrine QueryBuilder to query some data as bellow:

$id = '0062X00000vLZDVQA4';
$sql = $conn->createQueryBuilder()
->select(['Id', 'Name', 'Status__c'])
->from('Opportunity')
->where('Id = :id')
->andWhere('Name = :name')
->setParameter('name', 'Pay as you go Opportunity')
->setParameter('id', $id)
->setMaxResults(1)
->execute();
var_dump($sql->fetchAll()); // All rest api result

or use the normal Connection#query() method.

Basic Operations

Here are some examples of basic CRUD operations.

Connection#insert()

Creating an Account with the Name of John:

$connection->insert('Account', ['Name' => 'John']);

Connection#delete()

Deleting an Account with the Id = 1234:

$connection->delete('Account', ['Id' => '1234']);

Connection#update()

Update an Account with the Name of Sr. John where the Id is 1234:

$connection->update('Account', ['Name' => 'Sr. John'], ['Id' => '1234']);

Be Transactional with Composite API

As salesforce released the composite api, it gave us the ability to simulate transactions as in a database. So, we can use the same Doctrine DBAL api that you already know to do transactional operations in your Salesforce instance.

$conn->beginTransaction();
$conn->insert('Account', ['Name' => 'John']);
$conn->insert('Account', ['Name' => 'Elsa']);
$conn->commit();

Or even, use the Connection#transactional() helper, as you prefer.

Referencing another Records

The composite api, also enables us to compose a structure data to be changed in one single request. So we can cross reference records as it fits our needs.

Let's see how to create an Account and a linked Contact to that Account in a single composite request.

$conn->transactional(staticfunction () use ($conn) {
$conn->insert('Account', ['Name' => 'John'], ['referenceId' => 'account']);
$conn->insert('Contact', [
'FirstName' => 'John',
'LastName' => 'Contact',
'AccountId' => '@{account.id}'// reference `Account` by its `referenceId`
]);
});

🚫 Known Limitations

As of today, we cannot consume a sObject using the queryBuilder to get all fields from the sObject. That is because Salesforce doesn't accept SELECT * as a valid query.

The workaround that issue is to do a GET request to specific resources, then can grab all data related to that resource.

$this->connection
->getNativeConnection() // : \GuzzleHttp\ClientInterface
->request(
'GET',
sprintf('/services/data/v40.0/sobjects/Opportunity/%s', $id)
)
->getBody()
->getContents()
;

📈 Diagram

%%{init: {'sequence': { 'mirrorActors': false, 'rightAngles': true, 'messageAlign': 'center', 'actorFontSize': 20, 'actorFontWeight': 900, 'noteFontSize': 18, 'noteFontWeight': 600, 'messageFontSize': 20}}}%%
%%{init: {'theme': 'base', 'themeVariables': { 'actorBorder': '#D86613', 'activationBorderColor': '#232F3E', 'activationBkgColor': '#D86613','noteBorderColor': '#232F3E', 'signalColor': 'white', 'signalTextColor': 'gray', 'sequenceNumberColor': '#232F3E'}}}%%
sequenceDiagram
autonumber
Note left of ConnectionWrapper: Everything starts with <br/>the ConnectionWrapper.
ConnectionWrapper->>QueryBuilder: createQueryBuilder()
activate QueryBuilder
alt QueryBuilder->>QueryBuilder: execute() <br>Calls private executeQuery()<br>method
end
QueryBuilder->>+ConnectionWrapper: executeQuery()
deactivate QueryBuilder
ConnectionWrapper->>SoqlStatement: execute() SoqlStatement->>+\Doctrine\DBAL\Driver\Result: execute()
ConnectionWrapper->>+\Codelicia\Soql\DBAL\Result: new
\Doctrine\DBAL\Driver\Result-->>\Codelicia\Soql\DBAL\Result: pass to
\Codelicia\Soql\DBAL\Result-->>-ConnectionWrapper: returns
ConnectionWrapper->>-SoqlStatement: fetchAll()
SoqlStatement->>+\Codelicia\Soql\FetchDataUtility: fetchAll()
\Codelicia\Soql\FetchDataUtility-->>+\GuzzleHttp\ClientInterface: send()
Note right of \Codelicia\Soql\FetchDataUtility: Countable goes here?<br> before creating the Payload?
\Codelicia\Soql\FetchDataUtility->>+\Codelicia\Soql\Payload: new
\Codelicia\Soql\Payload-->>+SoqlStatement: returns
Loading

Author

About

A nice ✨ gambiarra ✨ to work with Salesforce SOQL Queries and Doctrine DBAL

Topics

Resources

Stars

5 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - codelicia/trineforce: A nice ✨ gambiarra ✨ to work with Salesforce SOQL Queries and Doctrine DBAL · GitHub
Skip to content

Repository files navigation

SOQL Doctrine DBAL

Salesforce Soql Doctrine Driver allows you to write Soql queries and interact with a Salesforce instance using the Doctrine DBAL layer.

Now one can forget about Salesforce and have a nice repository/query object integration on one's architecture without hurting that much on the usual project structure.

Installation

Use composer to install this package as bellow:

$ composer require codelicia/trineforce

Configuration

If you are familiar with Doctrine, then you probably already know how to configure and use it. But some special configuration is required in order to make it work.

When creating a new Connection, you should also provide the configuration keys for salesforceInstance, consumerKey, consumerSecret and point to the right driverClass. The usual user and password are also required.

$config = newConfiguration();
$connectionParams = [
'salesforceInstance' => 'https://[SALESFORCE INSTANCE].salesforce.com',
'apiVersion' => 'v43.0',
'user' => 'salesforce-user@email.com',
'password' => 'salesforce-password',
'consumerKey' => '...',
'consumerSecret' => '...',
'driverClass' => \Codelicia\Soql\SoqlDriver::class,
'wrapperClass' => \Codelicia\Soql\ConnectionWrapper::class,
];
/** @var \Codelicia\Soql\ConnectionWrapper $conn */$conn = DriverManager::getConnection($connectionParams, $config);
  • user provides the login, which is usually an email to access the salesforce instance.
  • password provides the corresponding password to the email provided on user.
  • salesforceInstance points to the url of the Salesforce instance.
  • apiVersion specify a salesforce API version to work with.
  • consumerKey provides the integration consumer key
  • consumerSecret provides the integration consumer secret
  • driverClass should points to \Codelicia\Soql\SoqlDriver::class
  • wrapperClass should points to \Codelicia\Soql\ConnectionWrapper::class

By setting up the wrapperClass, we can make use of a proper QueryBuild that allow JOIN in the Salesforce format.

When using the doctrine bundle and the dbal is configured through yaml the options should be passed in a different way for the validation that the bundle does.

doctrine:
dbal:
driver: soqluser: '%env(resolve:SALESFORCE_USERNAME)%'password: '%env(resolve:SALESFORCE_PASSWORD)%'driver_class: '\Codelicia\Soql\SoqlDriver'wrapper_class: '\Codelicia\Soql\ConnectionWrapper'options:
salesforceInstance: '%env(resolve:SALESFORCE_ENDPOINT)%'apiVersion: v56.0consumerKey: '%env(resolve:SALESFORCE_CLIENT_ID)%'consumerSecret: '%env(resolve:SALESFORCE_CLIENT_SECRET)%'

Using DBAL

Now that you have the connection set up, you can use Doctrine QueryBuilder to query some data as bellow:

$id = '0062X00000vLZDVQA4';
$sql = $conn->createQueryBuilder()
->select(['Id', 'Name', 'Status__c'])
->from('Opportunity')
->where('Id = :id')
->andWhere('Name = :name')
->setParameter('name', 'Pay as you go Opportunity')
->setParameter('id', $id)
->setMaxResults(1)
->execute();
var_dump($sql->fetchAll()); // All rest api result

or use the normal Connection#query() method.

Basic Operations

Here are some examples of basic CRUD operations.

Connection#insert()

Creating an Account with the Name of John:

$connection->insert('Account', ['Name' => 'John']);

Connection#delete()

Deleting an Account with the Id = 1234:

$connection->delete('Account', ['Id' => '1234']);

Connection#update()

Update an Account with the Name of Sr. John where the Id is 1234:

$connection->update('Account', ['Name' => 'Sr. John'], ['Id' => '1234']);

Be Transactional with Composite API

As salesforce released the composite api, it gave us the ability to simulate transactions as in a database. So, we can use the same Doctrine DBAL api that you already know to do transactional operations in your Salesforce instance.

$conn->beginTransaction();
$conn->insert('Account', ['Name' => 'John']);
$conn->insert('Account', ['Name' => 'Elsa']);
$conn->commit();

Or even, use the Connection#transactional() helper, as you prefer.

Referencing another Records

The composite api, also enables us to compose a structure data to be changed in one single request. So we can cross reference records as it fits our needs.

Let's see how to create an Account and a linked Contact to that Account in a single composite request.

$conn->transactional(staticfunction () use ($conn) {
$conn->insert('Account', ['Name' => 'John'], ['referenceId' => 'account']);
$conn->insert('Contact', [
'FirstName' => 'John',
'LastName' => 'Contact',
'AccountId' => '@{account.id}'// reference `Account` by its `referenceId`
]);
});

🚫 Known Limitations

As of today, we cannot consume a sObject using the queryBuilder to get all fields from the sObject. That is because Salesforce doesn't accept SELECT * as a valid query.

The workaround that issue is to do a GET request to specific resources, then can grab all data related to that resource.

$this->connection
->getNativeConnection() // : \GuzzleHttp\ClientInterface
->request(
'GET',
sprintf('/services/data/v40.0/sobjects/Opportunity/%s', $id)
)
->getBody()
->getContents()
;

📈 Diagram

%%{init: {'sequence': { 'mirrorActors': false, 'rightAngles': true, 'messageAlign': 'center', 'actorFontSize': 20, 'actorFontWeight': 900, 'noteFontSize': 18, 'noteFontWeight': 600, 'messageFontSize': 20}}}%%
%%{init: {'theme': 'base', 'themeVariables': { 'actorBorder': '#D86613', 'activationBorderColor': '#232F3E', 'activationBkgColor': '#D86613','noteBorderColor': '#232F3E', 'signalColor': 'white', 'signalTextColor': 'gray', 'sequenceNumberColor': '#232F3E'}}}%%
sequenceDiagram
autonumber
Note left of ConnectionWrapper: Everything starts with <br/>the ConnectionWrapper.
ConnectionWrapper->>QueryBuilder: createQueryBuilder()
activate QueryBuilder
alt QueryBuilder->>QueryBuilder: execute() <br>Calls private executeQuery()<br>method
end
QueryBuilder->>+ConnectionWrapper: executeQuery()
deactivate QueryBuilder
ConnectionWrapper->>SoqlStatement: execute() SoqlStatement->>+\Doctrine\DBAL\Driver\Result: execute()
ConnectionWrapper->>+\Codelicia\Soql\DBAL\Result: new
\Doctrine\DBAL\Driver\Result-->>\Codelicia\Soql\DBAL\Result: pass to
\Codelicia\Soql\DBAL\Result-->>-ConnectionWrapper: returns
ConnectionWrapper->>-SoqlStatement: fetchAll()
SoqlStatement->>+\Codelicia\Soql\FetchDataUtility: fetchAll()
\Codelicia\Soql\FetchDataUtility-->>+\GuzzleHttp\ClientInterface: send()
Note right of \Codelicia\Soql\FetchDataUtility: Countable goes here?<br> before creating the Payload?
\Codelicia\Soql\FetchDataUtility->>+\Codelicia\Soql\Payload: new
\Codelicia\Soql\Payload-->>+SoqlStatement: returns
Loading

Author

About

A nice ✨ gambiarra ✨ to work with Salesforce SOQL Queries and Doctrine DBAL

Topics

Resources

Stars

5 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - codelicia/trineforce: A nice ✨ gambiarra ✨ to work with Salesforce SOQL Queries and Doctrine DBAL · GitHub
Skip to content

Repository files navigation

SOQL Doctrine DBAL

Salesforce Soql Doctrine Driver allows you to write Soql queries and interact with a Salesforce instance using the Doctrine DBAL layer.

Now one can forget about Salesforce and have a nice repository/query object integration on one's architecture without hurting that much on the usual project structure.

Installation

Use composer to install this package as bellow:

$ composer require codelicia/trineforce

Configuration

If you are familiar with Doctrine, then you probably already know how to configure and use it. But some special configuration is required in order to make it work.

When creating a new Connection, you should also provide the configuration keys for salesforceInstance, consumerKey, consumerSecret and point to the right driverClass. The usual user and password are also required.

$config = newConfiguration();
$connectionParams = [
'salesforceInstance' => 'https://[SALESFORCE INSTANCE].salesforce.com',
'apiVersion' => 'v43.0',
'user' => 'salesforce-user@email.com',
'password' => 'salesforce-password',
'consumerKey' => '...',
'consumerSecret' => '...',
'driverClass' => \Codelicia\Soql\SoqlDriver::class,
'wrapperClass' => \Codelicia\Soql\ConnectionWrapper::class,
];
/** @var \Codelicia\Soql\ConnectionWrapper $conn */$conn = DriverManager::getConnection($connectionParams, $config);
  • user provides the login, which is usually an email to access the salesforce instance.
  • password provides the corresponding password to the email provided on user.
  • salesforceInstance points to the url of the Salesforce instance.
  • apiVersion specify a salesforce API version to work with.
  • consumerKey provides the integration consumer key
  • consumerSecret provides the integration consumer secret
  • driverClass should points to \Codelicia\Soql\SoqlDriver::class
  • wrapperClass should points to \Codelicia\Soql\ConnectionWrapper::class

By setting up the wrapperClass, we can make use of a proper QueryBuild that allow JOIN in the Salesforce format.

When using the doctrine bundle and the dbal is configured through yaml the options should be passed in a different way for the validation that the bundle does.

doctrine:
dbal:
driver: soqluser: '%env(resolve:SALESFORCE_USERNAME)%'password: '%env(resolve:SALESFORCE_PASSWORD)%'driver_class: '\Codelicia\Soql\SoqlDriver'wrapper_class: '\Codelicia\Soql\ConnectionWrapper'options:
salesforceInstance: '%env(resolve:SALESFORCE_ENDPOINT)%'apiVersion: v56.0consumerKey: '%env(resolve:SALESFORCE_CLIENT_ID)%'consumerSecret: '%env(resolve:SALESFORCE_CLIENT_SECRET)%'

Using DBAL

Now that you have the connection set up, you can use Doctrine QueryBuilder to query some data as bellow:

$id = '0062X00000vLZDVQA4';
$sql = $conn->createQueryBuilder()
->select(['Id', 'Name', 'Status__c'])
->from('Opportunity')
->where('Id = :id')
->andWhere('Name = :name')
->setParameter('name', 'Pay as you go Opportunity')
->setParameter('id', $id)
->setMaxResults(1)
->execute();
var_dump($sql->fetchAll()); // All rest api result

or use the normal Connection#query() method.

Basic Operations

Here are some examples of basic CRUD operations.

Connection#insert()

Creating an Account with the Name of John:

$connection->insert('Account', ['Name' => 'John']);

Connection#delete()

Deleting an Account with the Id = 1234:

$connection->delete('Account', ['Id' => '1234']);

Connection#update()

Update an Account with the Name of Sr. John where the Id is 1234:

$connection->update('Account', ['Name' => 'Sr. John'], ['Id' => '1234']);

Be Transactional with Composite API

As salesforce released the composite api, it gave us the ability to simulate transactions as in a database. So, we can use the same Doctrine DBAL api that you already know to do transactional operations in your Salesforce instance.

$conn->beginTransaction();
$conn->insert('Account', ['Name' => 'John']);
$conn->insert('Account', ['Name' => 'Elsa']);
$conn->commit();

Or even, use the Connection#transactional() helper, as you prefer.

Referencing another Records

The composite api, also enables us to compose a structure data to be changed in one single request. So we can cross reference records as it fits our needs.

Let's see how to create an Account and a linked Contact to that Account in a single composite request.

$conn->transactional(staticfunction () use ($conn) {
$conn->insert('Account', ['Name' => 'John'], ['referenceId' => 'account']);
$conn->insert('Contact', [
'FirstName' => 'John',
'LastName' => 'Contact',
'AccountId' => '@{account.id}'// reference `Account` by its `referenceId`
]);
});

🚫 Known Limitations

As of today, we cannot consume a sObject using the queryBuilder to get all fields from the sObject. That is because Salesforce doesn't accept SELECT * as a valid query.

The workaround that issue is to do a GET request to specific resources, then can grab all data related to that resource.

$this->connection
->getNativeConnection() // : \GuzzleHttp\ClientInterface
->request(
'GET',
sprintf('/services/data/v40.0/sobjects/Opportunity/%s', $id)
)
->getBody()
->getContents()
;

📈 Diagram

%%{init: {'sequence': { 'mirrorActors': false, 'rightAngles': true, 'messageAlign': 'center', 'actorFontSize': 20, 'actorFontWeight': 900, 'noteFontSize': 18, 'noteFontWeight': 600, 'messageFontSize': 20}}}%%
%%{init: {'theme': 'base', 'themeVariables': { 'actorBorder': '#D86613', 'activationBorderColor': '#232F3E', 'activationBkgColor': '#D86613','noteBorderColor': '#232F3E', 'signalColor': 'white', 'signalTextColor': 'gray', 'sequenceNumberColor': '#232F3E'}}}%%
sequenceDiagram
autonumber
Note left of ConnectionWrapper: Everything starts with <br/>the ConnectionWrapper.
ConnectionWrapper->>QueryBuilder: createQueryBuilder()
activate QueryBuilder
alt QueryBuilder->>QueryBuilder: execute() <br>Calls private executeQuery()<br>method
end
QueryBuilder->>+ConnectionWrapper: executeQuery()
deactivate QueryBuilder
ConnectionWrapper->>SoqlStatement: execute() SoqlStatement->>+\Doctrine\DBAL\Driver\Result: execute()
ConnectionWrapper->>+\Codelicia\Soql\DBAL\Result: new
\Doctrine\DBAL\Driver\Result-->>\Codelicia\Soql\DBAL\Result: pass to
\Codelicia\Soql\DBAL\Result-->>-ConnectionWrapper: returns
ConnectionWrapper->>-SoqlStatement: fetchAll()
SoqlStatement->>+\Codelicia\Soql\FetchDataUtility: fetchAll()
\Codelicia\Soql\FetchDataUtility-->>+\GuzzleHttp\ClientInterface: send()
Note right of \Codelicia\Soql\FetchDataUtility: Countable goes here?<br> before creating the Payload?
\Codelicia\Soql\FetchDataUtility->>+\Codelicia\Soql\Payload: new
\Codelicia\Soql\Payload-->>+SoqlStatement: returns
Loading

Author

About

A nice ✨ gambiarra ✨ to work with Salesforce SOQL Queries and Doctrine DBAL

Topics

Resources

Stars

5 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - codelicia/trineforce: A nice ✨ gambiarra ✨ to work with Salesforce SOQL Queries and Doctrine DBAL · GitHub
Skip to content

Repository files navigation

SOQL Doctrine DBAL

Salesforce Soql Doctrine Driver allows you to write Soql queries and interact with a Salesforce instance using the Doctrine DBAL layer.

Now one can forget about Salesforce and have a nice repository/query object integration on one's architecture without hurting that much on the usual project structure.

Installation

Use composer to install this package as bellow:

$ composer require codelicia/trineforce

Configuration

If you are familiar with Doctrine, then you probably already know how to configure and use it. But some special configuration is required in order to make it work.

When creating a new Connection, you should also provide the configuration keys for salesforceInstance, consumerKey, consumerSecret and point to the right driverClass. The usual user and password are also required.

$config = newConfiguration();
$connectionParams = [
'salesforceInstance' => 'https://[SALESFORCE INSTANCE].salesforce.com',
'apiVersion' => 'v43.0',
'user' => 'salesforce-user@email.com',
'password' => 'salesforce-password',
'consumerKey' => '...',
'consumerSecret' => '...',
'driverClass' => \Codelicia\Soql\SoqlDriver::class,
'wrapperClass' => \Codelicia\Soql\ConnectionWrapper::class,
];
/** @var \Codelicia\Soql\ConnectionWrapper $conn */$conn = DriverManager::getConnection($connectionParams, $config);
  • user provides the login, which is usually an email to access the salesforce instance.
  • password provides the corresponding password to the email provided on user.
  • salesforceInstance points to the url of the Salesforce instance.
  • apiVersion specify a salesforce API version to work with.
  • consumerKey provides the integration consumer key
  • consumerSecret provides the integration consumer secret
  • driverClass should points to \Codelicia\Soql\SoqlDriver::class
  • wrapperClass should points to \Codelicia\Soql\ConnectionWrapper::class

By setting up the wrapperClass, we can make use of a proper QueryBuild that allow JOIN in the Salesforce format.

When using the doctrine bundle and the dbal is configured through yaml the options should be passed in a different way for the validation that the bundle does.

doctrine:
dbal:
driver: soqluser: '%env(resolve:SALESFORCE_USERNAME)%'password: '%env(resolve:SALESFORCE_PASSWORD)%'driver_class: '\Codelicia\Soql\SoqlDriver'wrapper_class: '\Codelicia\Soql\ConnectionWrapper'options:
salesforceInstance: '%env(resolve:SALESFORCE_ENDPOINT)%'apiVersion: v56.0consumerKey: '%env(resolve:SALESFORCE_CLIENT_ID)%'consumerSecret: '%env(resolve:SALESFORCE_CLIENT_SECRET)%'

Using DBAL

Now that you have the connection set up, you can use Doctrine QueryBuilder to query some data as bellow:

$id = '0062X00000vLZDVQA4';
$sql = $conn->createQueryBuilder()
->select(['Id', 'Name', 'Status__c'])
->from('Opportunity')
->where('Id = :id')
->andWhere('Name = :name')
->setParameter('name', 'Pay as you go Opportunity')
->setParameter('id', $id)
->setMaxResults(1)
->execute();
var_dump($sql->fetchAll()); // All rest api result

or use the normal Connection#query() method.

Basic Operations

Here are some examples of basic CRUD operations.

Connection#insert()

Creating an Account with the Name of John:

$connection->insert('Account', ['Name' => 'John']);

Connection#delete()

Deleting an Account with the Id = 1234:

$connection->delete('Account', ['Id' => '1234']);

Connection#update()

Update an Account with the Name of Sr. John where the Id is 1234:

$connection->update('Account', ['Name' => 'Sr. John'], ['Id' => '1234']);

Be Transactional with Composite API

As salesforce released the composite api, it gave us the ability to simulate transactions as in a database. So, we can use the same Doctrine DBAL api that you already know to do transactional operations in your Salesforce instance.

$conn->beginTransaction();
$conn->insert('Account', ['Name' => 'John']);
$conn->insert('Account', ['Name' => 'Elsa']);
$conn->commit();

Or even, use the Connection#transactional() helper, as you prefer.

Referencing another Records

The composite api, also enables us to compose a structure data to be changed in one single request. So we can cross reference records as it fits our needs.

Let's see how to create an Account and a linked Contact to that Account in a single composite request.

$conn->transactional(staticfunction () use ($conn) {
$conn->insert('Account', ['Name' => 'John'], ['referenceId' => 'account']);
$conn->insert('Contact', [
'FirstName' => 'John',
'LastName' => 'Contact',
'AccountId' => '@{account.id}'// reference `Account` by its `referenceId`
]);
});

🚫 Known Limitations

As of today, we cannot consume a sObject using the queryBuilder to get all fields from the sObject. That is because Salesforce doesn't accept SELECT * as a valid query.

The workaround that issue is to do a GET request to specific resources, then can grab all data related to that resource.

$this->connection
->getNativeConnection() // : \GuzzleHttp\ClientInterface
->request(
'GET',
sprintf('/services/data/v40.0/sobjects/Opportunity/%s', $id)
)
->getBody()
->getContents()
;

📈 Diagram

%%{init: {'sequence': { 'mirrorActors': false, 'rightAngles': true, 'messageAlign': 'center', 'actorFontSize': 20, 'actorFontWeight': 900, 'noteFontSize': 18, 'noteFontWeight': 600, 'messageFontSize': 20}}}%%
%%{init: {'theme': 'base', 'themeVariables': { 'actorBorder': '#D86613', 'activationBorderColor': '#232F3E', 'activationBkgColor': '#D86613','noteBorderColor': '#232F3E', 'signalColor': 'white', 'signalTextColor': 'gray', 'sequenceNumberColor': '#232F3E'}}}%%
sequenceDiagram
autonumber
Note left of ConnectionWrapper: Everything starts with <br/>the ConnectionWrapper.
ConnectionWrapper->>QueryBuilder: createQueryBuilder()
activate QueryBuilder
alt QueryBuilder->>QueryBuilder: execute() <br>Calls private executeQuery()<br>method
end
QueryBuilder->>+ConnectionWrapper: executeQuery()
deactivate QueryBuilder
ConnectionWrapper->>SoqlStatement: execute() SoqlStatement->>+\Doctrine\DBAL\Driver\Result: execute()
ConnectionWrapper->>+\Codelicia\Soql\DBAL\Result: new
\Doctrine\DBAL\Driver\Result-->>\Codelicia\Soql\DBAL\Result: pass to
\Codelicia\Soql\DBAL\Result-->>-ConnectionWrapper: returns
ConnectionWrapper->>-SoqlStatement: fetchAll()
SoqlStatement->>+\Codelicia\Soql\FetchDataUtility: fetchAll()
\Codelicia\Soql\FetchDataUtility-->>+\GuzzleHttp\ClientInterface: send()
Note right of \Codelicia\Soql\FetchDataUtility: Countable goes here?<br> before creating the Payload?
\Codelicia\Soql\FetchDataUtility->>+\Codelicia\Soql\Payload: new
\Codelicia\Soql\Payload-->>+SoqlStatement: returns
Loading

Author

About

A nice ✨ gambiarra ✨ to work with Salesforce SOQL Queries and Doctrine DBAL

Topics

Resources

Stars

5 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - codelicia/trineforce: A nice ✨ gambiarra ✨ to work with Salesforce SOQL Queries and Doctrine DBAL · GitHub
Skip to content

Repository files navigation

SOQL Doctrine DBAL

Salesforce Soql Doctrine Driver allows you to write Soql queries and interact with a Salesforce instance using the Doctrine DBAL layer.

Now one can forget about Salesforce and have a nice repository/query object integration on one's architecture without hurting that much on the usual project structure.

Installation

Use composer to install this package as bellow:

$ composer require codelicia/trineforce

Configuration

If you are familiar with Doctrine, then you probably already know how to configure and use it. But some special configuration is required in order to make it work.

When creating a new Connection, you should also provide the configuration keys for salesforceInstance, consumerKey, consumerSecret and point to the right driverClass. The usual user and password are also required.

$config = newConfiguration();
$connectionParams = [
'salesforceInstance' => 'https://[SALESFORCE INSTANCE].salesforce.com',
'apiVersion' => 'v43.0',
'user' => 'salesforce-user@email.com',
'password' => 'salesforce-password',
'consumerKey' => '...',
'consumerSecret' => '...',
'driverClass' => \Codelicia\Soql\SoqlDriver::class,
'wrapperClass' => \Codelicia\Soql\ConnectionWrapper::class,
];
/** @var \Codelicia\Soql\ConnectionWrapper $conn */$conn = DriverManager::getConnection($connectionParams, $config);
  • user provides the login, which is usually an email to access the salesforce instance.
  • password provides the corresponding password to the email provided on user.
  • salesforceInstance points to the url of the Salesforce instance.
  • apiVersion specify a salesforce API version to work with.
  • consumerKey provides the integration consumer key
  • consumerSecret provides the integration consumer secret
  • driverClass should points to \Codelicia\Soql\SoqlDriver::class
  • wrapperClass should points to \Codelicia\Soql\ConnectionWrapper::class

By setting up the wrapperClass, we can make use of a proper QueryBuild that allow JOIN in the Salesforce format.

When using the doctrine bundle and the dbal is configured through yaml the options should be passed in a different way for the validation that the bundle does.

doctrine:
dbal:
driver: soqluser: '%env(resolve:SALESFORCE_USERNAME)%'password: '%env(resolve:SALESFORCE_PASSWORD)%'driver_class: '\Codelicia\Soql\SoqlDriver'wrapper_class: '\Codelicia\Soql\ConnectionWrapper'options:
salesforceInstance: '%env(resolve:SALESFORCE_ENDPOINT)%'apiVersion: v56.0consumerKey: '%env(resolve:SALESFORCE_CLIENT_ID)%'consumerSecret: '%env(resolve:SALESFORCE_CLIENT_SECRET)%'

Using DBAL

Now that you have the connection set up, you can use Doctrine QueryBuilder to query some data as bellow:

$id = '0062X00000vLZDVQA4';
$sql = $conn->createQueryBuilder()
->select(['Id', 'Name', 'Status__c'])
->from('Opportunity')
->where('Id = :id')
->andWhere('Name = :name')
->setParameter('name', 'Pay as you go Opportunity')
->setParameter('id', $id)
->setMaxResults(1)
->execute();
var_dump($sql->fetchAll()); // All rest api result

or use the normal Connection#query() method.

Basic Operations

Here are some examples of basic CRUD operations.

Connection#insert()

Creating an Account with the Name of John:

$connection->insert('Account', ['Name' => 'John']);

Connection#delete()

Deleting an Account with the Id = 1234:

$connection->delete('Account', ['Id' => '1234']);

Connection#update()

Update an Account with the Name of Sr. John where the Id is 1234:

$connection->update('Account', ['Name' => 'Sr. John'], ['Id' => '1234']);

Be Transactional with Composite API

As salesforce released the composite api, it gave us the ability to simulate transactions as in a database. So, we can use the same Doctrine DBAL api that you already know to do transactional operations in your Salesforce instance.

$conn->beginTransaction();
$conn->insert('Account', ['Name' => 'John']);
$conn->insert('Account', ['Name' => 'Elsa']);
$conn->commit();

Or even, use the Connection#transactional() helper, as you prefer.

Referencing another Records

The composite api, also enables us to compose a structure data to be changed in one single request. So we can cross reference records as it fits our needs.

Let's see how to create an Account and a linked Contact to that Account in a single composite request.

$conn->transactional(staticfunction () use ($conn) {
$conn->insert('Account', ['Name' => 'John'], ['referenceId' => 'account']);
$conn->insert('Contact', [
'FirstName' => 'John',
'LastName' => 'Contact',
'AccountId' => '@{account.id}'// reference `Account` by its `referenceId`
]);
});

🚫 Known Limitations

As of today, we cannot consume a sObject using the queryBuilder to get all fields from the sObject. That is because Salesforce doesn't accept SELECT * as a valid query.

The workaround that issue is to do a GET request to specific resources, then can grab all data related to that resource.

$this->connection
->getNativeConnection() // : \GuzzleHttp\ClientInterface
->request(
'GET',
sprintf('/services/data/v40.0/sobjects/Opportunity/%s', $id)
)
->getBody()
->getContents()
;

📈 Diagram

%%{init: {'sequence': { 'mirrorActors': false, 'rightAngles': true, 'messageAlign': 'center', 'actorFontSize': 20, 'actorFontWeight': 900, 'noteFontSize': 18, 'noteFontWeight': 600, 'messageFontSize': 20}}}%%
%%{init: {'theme': 'base', 'themeVariables': { 'actorBorder': '#D86613', 'activationBorderColor': '#232F3E', 'activationBkgColor': '#D86613','noteBorderColor': '#232F3E', 'signalColor': 'white', 'signalTextColor': 'gray', 'sequenceNumberColor': '#232F3E'}}}%%
sequenceDiagram
autonumber
Note left of ConnectionWrapper: Everything starts with <br/>the ConnectionWrapper.
ConnectionWrapper->>QueryBuilder: createQueryBuilder()
activate QueryBuilder
alt QueryBuilder->>QueryBuilder: execute() <br>Calls private executeQuery()<br>method
end
QueryBuilder->>+ConnectionWrapper: executeQuery()
deactivate QueryBuilder
ConnectionWrapper->>SoqlStatement: execute() SoqlStatement->>+\Doctrine\DBAL\Driver\Result: execute()
ConnectionWrapper->>+\Codelicia\Soql\DBAL\Result: new
\Doctrine\DBAL\Driver\Result-->>\Codelicia\Soql\DBAL\Result: pass to
\Codelicia\Soql\DBAL\Result-->>-ConnectionWrapper: returns
ConnectionWrapper->>-SoqlStatement: fetchAll()
SoqlStatement->>+\Codelicia\Soql\FetchDataUtility: fetchAll()
\Codelicia\Soql\FetchDataUtility-->>+\GuzzleHttp\ClientInterface: send()
Note right of \Codelicia\Soql\FetchDataUtility: Countable goes here?<br> before creating the Payload?
\Codelicia\Soql\FetchDataUtility->>+\Codelicia\Soql\Payload: new
\Codelicia\Soql\Payload-->>+SoqlStatement: returns
Loading

Author

About

A nice ✨ gambiarra ✨ to work with Salesforce SOQL Queries and Doctrine DBAL

Topics

Resources

Stars

5 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - codelicia/trineforce: A nice ✨ gambiarra ✨ to work with Salesforce SOQL Queries and Doctrine DBAL · GitHub
Skip to content

Repository files navigation

SOQL Doctrine DBAL

Salesforce Soql Doctrine Driver allows you to write Soql queries and interact with a Salesforce instance using the Doctrine DBAL layer.

Now one can forget about Salesforce and have a nice repository/query object integration on one's architecture without hurting that much on the usual project structure.

Installation

Use composer to install this package as bellow:

$ composer require codelicia/trineforce

Configuration

If you are familiar with Doctrine, then you probably already know how to configure and use it. But some special configuration is required in order to make it work.

When creating a new Connection, you should also provide the configuration keys for salesforceInstance, consumerKey, consumerSecret and point to the right driverClass. The usual user and password are also required.

$config = newConfiguration();
$connectionParams = [
'salesforceInstance' => 'https://[SALESFORCE INSTANCE].salesforce.com',
'apiVersion' => 'v43.0',
'user' => 'salesforce-user@email.com',
'password' => 'salesforce-password',
'consumerKey' => '...',
'consumerSecret' => '...',
'driverClass' => \Codelicia\Soql\SoqlDriver::class,
'wrapperClass' => \Codelicia\Soql\ConnectionWrapper::class,
];
/** @var \Codelicia\Soql\ConnectionWrapper $conn */$conn = DriverManager::getConnection($connectionParams, $config);
  • user provides the login, which is usually an email to access the salesforce instance.
  • password provides the corresponding password to the email provided on user.
  • salesforceInstance points to the url of the Salesforce instance.
  • apiVersion specify a salesforce API version to work with.
  • consumerKey provides the integration consumer key
  • consumerSecret provides the integration consumer secret
  • driverClass should points to \Codelicia\Soql\SoqlDriver::class
  • wrapperClass should points to \Codelicia\Soql\ConnectionWrapper::class

By setting up the wrapperClass, we can make use of a proper QueryBuild that allow JOIN in the Salesforce format.

When using the doctrine bundle and the dbal is configured through yaml the options should be passed in a different way for the validation that the bundle does.

doctrine:
dbal:
driver: soqluser: '%env(resolve:SALESFORCE_USERNAME)%'password: '%env(resolve:SALESFORCE_PASSWORD)%'driver_class: '\Codelicia\Soql\SoqlDriver'wrapper_class: '\Codelicia\Soql\ConnectionWrapper'options:
salesforceInstance: '%env(resolve:SALESFORCE_ENDPOINT)%'apiVersion: v56.0consumerKey: '%env(resolve:SALESFORCE_CLIENT_ID)%'consumerSecret: '%env(resolve:SALESFORCE_CLIENT_SECRET)%'

Using DBAL

Now that you have the connection set up, you can use Doctrine QueryBuilder to query some data as bellow:

$id = '0062X00000vLZDVQA4';
$sql = $conn->createQueryBuilder()
->select(['Id', 'Name', 'Status__c'])
->from('Opportunity')
->where('Id = :id')
->andWhere('Name = :name')
->setParameter('name', 'Pay as you go Opportunity')
->setParameter('id', $id)
->setMaxResults(1)
->execute();
var_dump($sql->fetchAll()); // All rest api result

or use the normal Connection#query() method.

Basic Operations

Here are some examples of basic CRUD operations.

Connection#insert()

Creating an Account with the Name of John:

$connection->insert('Account', ['Name' => 'John']);

Connection#delete()

Deleting an Account with the Id = 1234:

$connection->delete('Account', ['Id' => '1234']);

Connection#update()

Update an Account with the Name of Sr. John where the Id is 1234:

$connection->update('Account', ['Name' => 'Sr. John'], ['Id' => '1234']);

Be Transactional with Composite API

As salesforce released the composite api, it gave us the ability to simulate transactions as in a database. So, we can use the same Doctrine DBAL api that you already know to do transactional operations in your Salesforce instance.

$conn->beginTransaction();
$conn->insert('Account', ['Name' => 'John']);
$conn->insert('Account', ['Name' => 'Elsa']);
$conn->commit();

Or even, use the Connection#transactional() helper, as you prefer.

Referencing another Records

The composite api, also enables us to compose a structure data to be changed in one single request. So we can cross reference records as it fits our needs.

Let's see how to create an Account and a linked Contact to that Account in a single composite request.

$conn->transactional(staticfunction () use ($conn) {
$conn->insert('Account', ['Name' => 'John'], ['referenceId' => 'account']);
$conn->insert('Contact', [
'FirstName' => 'John',
'LastName' => 'Contact',
'AccountId' => '@{account.id}'// reference `Account` by its `referenceId`
]);
});

🚫 Known Limitations

As of today, we cannot consume a sObject using the queryBuilder to get all fields from the sObject. That is because Salesforce doesn't accept SELECT * as a valid query.

The workaround that issue is to do a GET request to specific resources, then can grab all data related to that resource.

$this->connection
->getNativeConnection() // : \GuzzleHttp\ClientInterface
->request(
'GET',
sprintf('/services/data/v40.0/sobjects/Opportunity/%s', $id)
)
->getBody()
->getContents()
;

📈 Diagram

%%{init: {'sequence': { 'mirrorActors': false, 'rightAngles': true, 'messageAlign': 'center', 'actorFontSize': 20, 'actorFontWeight': 900, 'noteFontSize': 18, 'noteFontWeight': 600, 'messageFontSize': 20}}}%%
%%{init: {'theme': 'base', 'themeVariables': { 'actorBorder': '#D86613', 'activationBorderColor': '#232F3E', 'activationBkgColor': '#D86613','noteBorderColor': '#232F3E', 'signalColor': 'white', 'signalTextColor': 'gray', 'sequenceNumberColor': '#232F3E'}}}%%
sequenceDiagram
autonumber
Note left of ConnectionWrapper: Everything starts with <br/>the ConnectionWrapper.
ConnectionWrapper->>QueryBuilder: createQueryBuilder()
activate QueryBuilder
alt QueryBuilder->>QueryBuilder: execute() <br>Calls private executeQuery()<br>method
end
QueryBuilder->>+ConnectionWrapper: executeQuery()
deactivate QueryBuilder
ConnectionWrapper->>SoqlStatement: execute() SoqlStatement->>+\Doctrine\DBAL\Driver\Result: execute()
ConnectionWrapper->>+\Codelicia\Soql\DBAL\Result: new
\Doctrine\DBAL\Driver\Result-->>\Codelicia\Soql\DBAL\Result: pass to
\Codelicia\Soql\DBAL\Result-->>-ConnectionWrapper: returns
ConnectionWrapper->>-SoqlStatement: fetchAll()
SoqlStatement->>+\Codelicia\Soql\FetchDataUtility: fetchAll()
\Codelicia\Soql\FetchDataUtility-->>+\GuzzleHttp\ClientInterface: send()
Note right of \Codelicia\Soql\FetchDataUtility: Countable goes here?<br> before creating the Payload?
\Codelicia\Soql\FetchDataUtility->>+\Codelicia\Soql\Payload: new
\Codelicia\Soql\Payload-->>+SoqlStatement: returns
Loading

Author

About

A nice ✨ gambiarra ✨ to work with Salesforce SOQL Queries and Doctrine DBAL

Topics

Resources

Stars

5 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages