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