開発情報・ナレッジ

投稿者: ShiningStar株式会社 2025年9月29日 (月)

salesforceレコード連携サンプルプログラム

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

解決しない場合はこちら コンテンツに関しての
要望はこちら