この記事ではSPIRALからSalesforceへのレコード連携について、メソッドごとにサンプルコードをまとめています。
SPIRAL ver.1 とSalesforceをAPI連携したアプリケーションを構築する時の参考になれば幸いです。
SalesforceのREST APIについてはこちらをご確認ください。
共通モジュール
//------------------------------ // 共通モジュール 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); $http_status = curl_getinfo($curl, CURLINFO_HTTP_CODE); $curl_error = curl_error($curl); if ($response === false) { throw new Exception('Salesforce authentication failed: ' . $curl_error); } 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 v1 API ヘルパ //============================== function spiral_generate_signature($token, $passkey, $secret) { $message = $token . '&' . $passkey; return hash_hmac('sha1', $message, $secret); } function spiral_post_json($url, array $headers, array $payload) { $h = []; foreach ($headers as $k => $v) $h[] = $k . ': ' . $v; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => $h, CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE), ]); $res = curl_exec($ch); $err = curl_error($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($res === false) throw new Exception("SPIRAL request error: $err"); if ($code !== 200) throw new Exception("SPIRAL HTTP $code: $res"); $json = json_decode($res, true); if ($json === null) throw new Exception("SPIRAL invalid JSON: $res"); return $json; } function spiral_get_api_url() { $headers = [ 'X-SPIRAL-API' => 'locator/apiserver/request', 'Content-Type' => 'application/json; charset=UTF-8', ]; $payload = ['spiral_api_token' => SPIRAL_API_TOKEN]; $json = spiral_post_json(SPIRAL_LOCATOR_URL, $headers, $payload); if (empty($json['location'])) { throw new Exception('Locator API failed: ' . json_encode($json, JSON_UNESCAPED_UNICODE)); } return $json['location']; } /** * database/get_file/request でファイルを取得(Base64→バイナリ) * @param string $key_value レコードのキー(例: idの値) * @return array [binaryData, fileName] 添付がなければ [null, null] */ function spiral_get_file_binary_and_name($key_value) { $apiUrl = spiral_get_api_url(); $endpointHeaders = [ 'X-SPIRAL-API' => 'database/get_file/request', 'Content-Type' => 'application/json; charset=UTF-8', ]; $passkey = time(); $payload = [ 'spiral_api_token' => SPIRAL_API_TOKEN, 'passkey' => $passkey, 'db_title' => SPIRAL_DB_TITLE, 'file_field_title' => SPIRAL_FILE_FIELD_TITLE, 'key_field_title' => SPIRAL_KEY_FIELD_TITLE, 'key_field_value' => $key_value, 'signature' => spiral_generate_signature(SPIRAL_API_TOKEN, $passkey, SPIRAL_API_SECRET), ]; $json = spiral_post_json($apiUrl, $endpointHeaders, $payload); $fileName = $json['file_name'] ?? null; $data64 = $json['data'] ?? null; if (!$fileName || !$data64) { return [null, null]; } $binary = base64_decode($data64, true); if ($binary === false) { throw new Exception('Failed to base64_decode SPIRAL file data.'); } return [$binary, $fileName]; }
設定値
<?php //------------------------------ // 設定値 //------------------------------ // 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'); //カスタムオブジェクトのオブジェクト名を指定 //------------------------------ // SPIRAL v1 API 設定(locator → database/*) //------------------------------ define('SPIRAL_LOCATOR_URL', 'https://www.pi-pe.co.jp/api/locator'); define('SPIRAL_API_TOKEN', 'YOUR_SPIRAL_API_TOKEN'); define('SPIRAL_API_SECRET', 'YOUR_SPIRAL_API_SECRET'); // ファイル取得対象DBと項目名 define('SPIRAL_DB_TITLE', 'salesforce'); // 取得したいSPIRALのDBタイトル define('SPIRAL_FILE_FIELD_TITLE', 'file'); // ファイル型フィールドの差し替えキーワード define('SPIRAL_KEY_FIELD_TITLE', 'spiral_id'); // 主キーのフィールドの差し替えキーワード
使用例
//------------------------------ // 総合的な使用例 //------------------------------ try { // 1. SPIRALから連携対象のデータを取得 $db = $SPIRAL->getDataBase("salesforce"); $db->addSelectFields("spiral_id", "name", "mail", "file"); $db->setLinesPerPage(1000); $spiralRecords = $db->doSelect(); $spiralRecords = $spiralRecords["data"]; if (empty($spiralRecords)) { echo "No records to process from SPIRAL.\n"; return; } // 2. 登録・更新処理のループ foreach ($spiralRecords as $record) { // SPIRALとSalesforceのフィールドマッピングを定義 // SPIRALのレコードIDをSalesforceの外部IDとして使用する例 $fieldMapping = [ 'spiral_id' => 'SPIRAL_ID__c', // SPIRALのレコードID -> Salesforceの外部ID項目 'name' => 'Name', // SPIRALの「会社名」 -> Salesforceの「取引先名」 'mail' => 'mail__c', // SPIRALの「電話番号」 -> Salesforceの「電話」 // ... 他のフィールドマッピング ]; $sfObject = SALESFORCE_OBJECT_NAME; // 3. Salesforceにレコードを登録 $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['spiral_id']}\n"; } // 5. SPIRALのファイルをSalesforceに連携 if (!empty($record[SPIRAL_KEY_FIELD_TITLE])) { $keyValue = $record[SPIRAL_KEY_FIELD_TITLE]; $cv = linkFileFromSpiralToSalesforce([], $creationResult['id'], $keyValue); if (!empty($cv['id'])) { echo "File linked from SPIRAL to Salesforce record.\n"; } elseif (!empty($cv['skipped'])) { echo "No file in SPIRAL record (skipped).\n"; } } // 6. Salesforceのレコードを削除 if (deleteSalesforceRecordByExternalId($sfObject, $externalIdField, $record['spiral_id'])) { echo "Salesforce record deleted successfully using external ID: {$record['spiral_id']}\n"; } echo "--------------------\n"; } } catch (Exception $e) { echo 'An error occurred: ' . $e->getMessage(); }
実行前に読み込むようにしてください。
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; } }
ファイル連携
//------------------------------ // SPIRALからSalesforceへファイル連携 //------------------------------ function linkFileFromSpiralToSalesforce($spiralInfo, $relatedSalesforceRecordId = null, $keyValue = null) { try { if ($keyValue === null) { throw new Exception('SPIRAL key value is required to get file. (e.g., id)'); } // 1) SPIRAL(v1)からファイル取得 list($fileBinary, $fileName) = spiral_get_file_binary_and_name($keyValue); if (!$fileBinary || !$fileName) { return ['skipped' => true, 'reason' => 'no_file']; } // 2) Salesforceにアップロード(ContentVersion) $salesforceConnector = new SalesforceConnector(); $contentVersionData = [ 'Title' => $fileName, 'PathOnClient' => $fileName, 'VersionData' => base64_encode($fileBinary), ]; $cvEndpoint = '/services/data/v58.0/sobjects/ContentVersion'; $cvResponse = $salesforceConnector->request('POST', $cvEndpoint, $contentVersionData); if (empty($cvResponse['id'])) { $msg = isset($cvResponse[0]['message']) ? $cvResponse[0]['message'] : 'Failed to upload file to Salesforce.'; throw new Exception($msg); } // 3) レコード関連付け(任意) if ($relatedSalesforceRecordId) { $soql = "SELECT ContentDocumentId FROM ContentVersion WHERE Id = '{$cvResponse['id']}'"; $queryEndpoint = '/services/data/v58.0/query/?q=' . urlencode($soql); $queryResponse = $salesforceConnector->request('GET', $queryEndpoint); if (empty($queryResponse['records'][0]['ContentDocumentId'])) { throw new Exception('Failed to fetch ContentDocumentId.'); } $contentDocumentId = $queryResponse['records'][0]['ContentDocumentId']; $linkData = [ 'ContentDocumentId' => $contentDocumentId, 'LinkedEntityId' => $relatedSalesforceRecordId, 'ShareType' => 'V', ]; $linkEndpoint = '/services/data/v58.0/sobjects/ContentDocumentLink'; $salesforceConnector->request('POST', $linkEndpoint, $linkData); } return $cvResponse; } catch (Exception $e) { echo 'Error: ' . $e->getMessage(); return []; } }