この記事では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 []; } }