この記事ではSPIRALからSalesforceへのレコード連携について、メソッドごとにサンプルコードをまとめています。
SPIRAL ver.2 とSalesforceをAPI連携したアプリケーションを構築する時の参考になれば幸いです。
SalesforceのREST APIについてはこちらをご確認ください。
共通モジュール
<?php
//------------------------------
// 共通モジュール SalesforceConnector
//------------------------------
class SalesforceConnector {
private $accessToken;
private $instanceUrl;
/**
* コンストラクタで認証を行い、アクセストークンとインスタンスURLを取得します。
*/
public function __construct() {
$this->authenticate();
}
/**
* Salesforceに認証してアクセストークンを取得します。
*/
private function authenticate() {
$params = [
'grant_type' => 'password',
'client_id' => SALESFORCE_CLIENT_ID,
'client_secret' => SALESFORCE_CLIENT_SECRET,
'username' => SALESFORCE_USERNAME,
'password' => SALESFORCE_PASSWORD . SALESFORCE_SECURITY_TOKEN,
];
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, SALESFORCE_LOGIN_URL . '/services/oauth2/token');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($params));
$response = curl_exec($curl);
if (curl_errno($curl)) {
throw new Exception('Salesforce authentication failed: ' . curl_error($curl));
}
curl_close($curl);
$result = json_decode($response, true);
if (isset($result['error'])) {
throw new Exception('Salesforce authentication error: ' . $result['error_description']);
}
$this->accessToken = $result['access_token'];
$this->instanceUrl = $result['instance_url'];
}
/**
* Salesforce APIへのリクエストを実行します。
* @param string $method HTTPメソッド (GET, POST, PATCH, DELETE)
* @param string $endpoint APIエンドポイント
* @param array|null $data 送信するデータ
* @return array APIレスポンス
*/
public function request($method, $endpoint, $data = null) {
$url = $this->instanceUrl . $endpoint;
$header = [
'Authorization: Bearer ' . $this->accessToken,
'Content-Type: application/json',
];
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
if ($data) {
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
}
$response = curl_exec($curl);
if (curl_errno($curl)) {
throw new Exception('Salesforce API request failed: ' . curl_error($curl));
}
curl_close($curl);
return json_decode($response, true);
}
}
//------------------------------
// 共通モジュール spiral
//------------------------------
class CommonBase {
/**
* シングルトンインスタンス
* @var UserManager
*/
protected static $singleton;
public function __construct() {
if (self::$singleton) {
throw new Exception('must be singleton');
}
self::$singleton = $this;
}
/**
* シングルトンインスタンスを返す
* @return UserManager
*/
public static function getInstance() {
if (!self::$singleton) {
return new CommonBase();
} else {
return self::$singleton;
}
}
/**
* V2用 API送信ロジック
* @return Result
*/
function apiCurlAction($method, $addUrlPass, $data = null, $multiPart = null, $jsonDecode = null) {
$header = array(
"Authorization:Bearer ". API_KEY,
"X-Spiral-Api-Version: 1.1",
);
if($multiPart) {
$header = array_merge($header, array($multiPart));
} else {
$header = array_merge($header, array("Content-Type:application/json"));
}
if(APP_ROLE){
$header = array_merge($header, array("X-Spiral-App-Role: ".APP_ROLE));
}
// curl
$curl = curl_init();
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_URL, API_URL. $addUrlPass);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
if ($method == "POST") {
if ($multiPart) {
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
} else {
curl_setopt($curl, CURLOPT_POSTFIELDS , json_encode($data));
}
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
}
if ($method == "PATCH") {
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
}
if ($method == "DELETE") {
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
}
$response = curl_exec($curl);
if (curl_errno($curl)) echo curl_error($curl);
curl_close($curl);
if($jsonDecode){
return $response;
}else{
return json_decode($response, true);
}
}
}
設定値
<?php
//------------------------------
// 設定値
//------------------------------
// SPIRAL API設定
define("API_URL", "https://api.spiral-platform.com/v1");
define("API_KEY", "APIキー");
define("APP_ROLE", "アプリロール識別名");
define("APP_ID", "アプリID");
define("DB_ID", "DBID");
// SPIRAL DB設定
define("FILE_FIELD_NAME", "file"); //ファイルフィールドの識別名を指定
define("FILE_FIELD_ID", "3"); //ファイルフィールドのIDを指定
// Salesforce ログイン情報
define('SALESFORCE_LOGIN_URL', 'https://login.salesforce.com'); // Or your sandbox URL
// Salesforce 接続アプリケーションのコンシューマキーとコンシューマの秘密
define('SALESFORCE_CLIENT_ID', 'YOUR_CLIENT_ID');
define('SALESFORCE_CLIENT_SECRET', 'YOUR_CLIENT_SECRET');
// Salesforce ユーザの認証情報
define('SALESFORCE_USERNAME', 'YOUR_USERNAME');
define('SALESFORCE_PASSWORD', 'YOUR_PASSWORD');
define('SALESFORCE_SECURITY_TOKEN', 'YOUR_SECURITY_TOKEN');
// Salesforce オブジェクト設定
define('SALESFORCE_OBJECT_NAME', 'spiral__c'); //カスタムオブジェクトのオブジェクト名を指定
使用例
<?php
//------------------------------
// 総合的な使用例
//------------------------------
try {
// 1. SPIRALから連携対象のデータを取得
$spiralAppId = APP_ID;
$spiralDbId = DB_ID;
$spiralRecords = getSpiralRecords($spiralAppId, $spiralDbId);
if (empty($spiralRecords)) {
echo "No records to process from SPIRAL.\n";
return;
}
// 2. 登録・更新処理のループ
foreach ($spiralRecords as $record) {
// SPIRALとSalesforceのフィールドマッピングを定義
// SPIRALのレコードIDをSalesforceの外部IDとして使用する例
$fieldMapping = [
'_id' => 'SPIRAL_ID__c', // SPIRALのレコードID -> Salesforceの外部ID項目
'field_1' => 'Name', // SPIRALの「会社名」 -> Salesforceの「取引先名」
'field_2' => 'Phone', // SPIRALの「電話番号」 -> Salesforceの「電話」
// ... 他のフィールドマッピング
];
// 3. Salesforceにレコードを登録
$sfObject = SALESFORCE_OBJECT_NAME;
$creationResult = createSalesforceRecordFromSpiral($sfObject, $record, $fieldMapping);
echo "Salesforce record created with ID: {$creationResult['id']}\n";
// 4. Salesforceのレコードを更新
$externalIdField = 'SPIRAL_ID__c'; //Salesforceの外部ID項目
if (updateSalesforceRecordFromSpiral($sfObject, $externalIdField, $record, $fieldMapping)) {
echo "Salesforce record updated successfully using external ID: {$record['_id']}\n";
}
// 5. SPIRALのファイルをSalesforceに連携
$fileFieldName = FILE_FIELD_NAME; // ファイルフィールドのフィールドIDをここに指定
$fileFieldId = FILE_FIELD_ID; // ファイルフィールドのフィールドIDをここに指定
if (!empty($record[$fileFieldName])) {
$spiralFileInfo = [
'appId' => $spiralAppId,
'dbId' => $spiralDbId,
'recordId' => $record['_id'],
'fieldId' => $fileFieldId,
'fileKey' => $record[$fileFieldName][0]['fileKey'],
'fileName' => $record[$fileFieldName][0]['fileName']
];
linkFileFromSpiralToSalesforce($spiralFileInfo, $creationResult['id']);
echo "File linked from SPIRAL to Salesforce record.\n";
}
// 6. Salesforceのレコードを削除
if (deleteSalesforceRecordByExternalId($sfObject, $externalIdField, $record['_id'])) {
echo "Salesforce record deleted successfully using external ID: {$record['_id']}\n";
}
echo "--------------------\n";
}
} catch (Exception $e) {
echo 'An error occurred: ' . $e->getMessage();
}
実行前に読み込むようにしてください。
SPIRALからのレコード取得
<?php
//------------------------------
// SPIRALからレコード取得
//------------------------------
/**
* SPIRAL DBからレコードを取得します。
* @param string $appId SPIRALのアプリケーションID
* @param string $dbId SPIRALのDB ID
* @param array $search_condition 検索条件
* @return array 取得したレコード
*/
function getSpiralRecords($appId, $dbId, $search_condition = []) {
$commonBase = CommonBase::getInstance();
$path = "/apps/{$appId}/dbs/{$dbId}/records";
$request_data = [];
// apiCurlActionの第5引数をtrueにするとjson_decode前のレスポンスが返る
$response = $commonBase->apiCurlAction('GET', $path, $request_data, null, false);
if (isset($response['items'])) {
return $response['items'];
} else {
// エラーハンドリング
$errorMessage = isset($response['errors'][0]['message']) ? $response['errors'][0]['message'] : 'Failed to get records from SPIRAL.';
throw new Exception($errorMessage);
}
}
Salesforceへのレコード登録
<?php
//------------------------------
// Salesforceへレコード登録
//------------------------------
/**
* SPIRALのデータをSalesforceに登録します。
* @param string $objectName Salesforceのオブジェクト名
* @param array $spiralRecord SPIRALから取得した単一レコードデータ
* @param array $fieldMapping SPIRALとSalesforceのフィールド名マッピング
* @return array 登録結果
*/
function createSalesforceRecordFromSpiral($objectName, $spiralRecord, $fieldMapping) {
$salesforceData = [];
foreach ($fieldMapping as $spiralField => $salesforceField) {
if (isset($spiralRecord[$spiralField])) {
$salesforceData[$salesforceField] = $spiralRecord[$spiralField];
}
}
if (empty($salesforceData)) {
throw new Exception('No data to create in Salesforce.');
}
try {
$connector = new SalesforceConnector();
$endpoint = '/services/data/v58.0/sobjects/' . $objectName;
$response = $connector->request('POST', $endpoint, $salesforceData);
if (isset($response['id'])) {
return $response;
} else {
// エラーハンドリング
$errorMessage = isset($response[0]['message']) ? $response[0]['message'] : 'Failed to create Salesforce record.';
throw new Exception($errorMessage);
}
} catch (Exception $e) {
// 例外処理
echo 'Error: ' . $e->getMessage();
return [];
}
}
Salesforceのレコード更新
<?php
//------------------------------
// Salesforceのレコード更新
//------------------------------
/**
* SPIRALのデータをもとにSalesforceのレコードを更新します。
* @param string $objectName Salesforceのオブジェクト名
* @param string $externalIdField Salesforceの外部ID項目名
* @param array $spiralRecord SPIRALから取得した単一レコードデータ
* @param array $fieldMapping SPIRALとSalesforceのフィールド名マッピング
* @return bool 更新が成功したかどうか
*/
function updateSalesforceRecordFromSpiral($objectName, $externalIdField, $spiralRecord, $fieldMapping) {
$spiralExternalIdField = array_search($externalIdField, $fieldMapping);
if ($spiralExternalIdField === false || !isset($spiralRecord[$spiralExternalIdField])) {
throw new Exception("External ID field '{$spiralExternalIdField}' not found in SPIRAL record or mapping.");
}
$externalIdValue = $spiralRecord[$spiralExternalIdField];
$salesforceData = [];
foreach ($fieldMapping as $spiralField => $salesforceField) {
// 外部ID項目は更新データに含めない
if ($salesforceField !== $externalIdField && isset($spiralRecord[$spiralField])) {
$salesforceData[$salesforceField] = $spiralRecord[$spiralField];
}
}
if (empty($salesforceData)) {
throw new Exception('No data to update in Salesforce.');
}
try {
$connector = new SalesforceConnector();
// 外部IDを使用してレコードを更新
$endpoint = '/services/data/v58.0/sobjects/' . $objectName . '/' . $externalIdField . '/' . $externalIdValue;
$connector->request('PATCH', $endpoint, $salesforceData);
// 更新が成功すると、レスポンスは空 (HTTP 204 No Content) になる
return true;
} catch (Exception $e) {
// 例外処理
echo 'Error: ' . $e->getMessage();
return false;
}
}
Salesforceのレコード削除
<?php
//------------------------------
// Salesforceのレコード削除
//------------------------------
/**
* 外部IDを使用してSalesforceのレコードを削除します。
* @param string $objectName Salesforceのオブジェクト名
* @param string $externalIdField Salesforceの外部ID項目名
* @param string $externalIdValue 削除するレコードの外部IDの値
* @return bool 削除が成功したかどうか
*/
function deleteSalesforceRecordByExternalId($objectName, $externalIdField, $externalIdValue) {
try {
$connector = new SalesforceConnector();
$endpoint = '/services/data/v58.0/sobjects/' . $objectName . '/' . $externalIdField . '/' . $externalIdValue;
$connector->request('DELETE', $endpoint);
// 削除が成功すると、レスポンスは空 (HTTP 204 No Content) になる
return true;
} catch (Exception $e) {
// 例外処理
echo 'Error: ' . $e->getMessage();
return false;
}
}
ファイル連携
<?php
//------------------------------
// SPIRALからSalesforceへファイル連携
//------------------------------
/**
* SPIRALのファイル項目からファイルを取得し、Salesforceにアップロードします。
* @param array $spiralInfo SPIRALのファイル情報 [appId, dbId, recordId, fieldId, fileKey, fileName]
* @param string|null $relatedSalesforceRecordId 関連付けるSalesforceレコードのID (任意)
* @return array アップロード結果
*/
function linkFileFromSpiralToSalesforce($spiralInfo, $relatedSalesforceRecordId = null) {
try {
// 1. SPIRALからファイルデータを取得
$commonBase = CommonBase::getInstance();
$path = "/apps/{$spiralInfo['appId']}/dbs/{$spiralInfo['dbId']}/{$spiralInfo['fieldId']}/{$spiralInfo['recordId']}/files/{$spiralInfo['fileKey']}/download";
$fileData = $commonBase->apiCurlAction('GET', $path, null, null, true); // trueでrawデータを取得
if (!$fileData) {
throw new Exception('Failed to download file from SPIRAL.');
}
// 2. Salesforceにファイルをアップロード (ContentVersionを作成)
$salesforceConnector = new SalesforceConnector();
$contentVersionData = [
'Title' => $spiralInfo['fileName'],
'PathOnClient' => $spiralInfo['fileName'],
'VersionData' => base64_encode($fileData)
];
$cvEndpoint = '/services/data/v58.0/sobjects/ContentVersion';
$cvResponse = $salesforceConnector->request('POST', $cvEndpoint, $contentVersionData);
if (!isset($cvResponse['id'])) {
$errorMessage = isset($cvResponse[0]['message']) ? $cvResponse[0]['message'] : 'Failed to upload file to Salesforce.';
throw new Exception($errorMessage);
}
// 3. (任意) Salesforceレコードに関連付ける
if ($relatedSalesforceRecordId) {
// ContentDocumentId を取得
$soql = "SELECT ContentDocumentId FROM ContentVersion WHERE Id = '{$cvResponse['id']}'";
$queryEndpoint = '/services/data/v58.0/query/?q=' . urlencode($soql);
$queryResponse = $salesforceConnector->request('GET', $queryEndpoint);
$contentDocumentId = $queryResponse['records'][0]['ContentDocumentId'];
// ContentDocumentLink を作成して関連付け
$linkData = [
'ContentDocumentId' => $contentDocumentId,
'LinkedEntityId' => $relatedSalesforceRecordId,
'ShareType' => 'V' // V: Viewer, C: Collaborator, I: Inferred
];
$linkEndpoint = '/services/data/v58.0/sobjects/ContentDocumentLink';
$salesforceConnector->request('POST', $linkEndpoint, $linkData);
}
return $cvResponse;
} catch (Exception $e) {
// 例外処理
echo 'Error: ' . $e->getMessage();
return [];
}
}