開発情報・ナレッジ

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

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

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

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