開発情報・ナレッジ

投稿者: ShiningStar株式会社 2024年2月26日 (月)

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

この記事ではSPIRALから株式会社サイボウズのkintoneへのレコード連携について、メソッドごとにサンプルコードをまとめています。
SPIRAL ver.2 とkintoneをAPI連携したアプリケーションを構築する時の参考になれば幸いです。


共通モジュール

<?php
//------------------------------
// 共通モジュール
//------------------------------
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);
		}
    }
}
class KintoneAPI {

    private $subdomain;
    private $apiToken;
    private $user;
    private $password;

    public function __construct($subdomain, $apiToken = '', $user = '', $password = '') {
        $this->subdomain = $subdomain;
        $this->apiToken = $apiToken;
        $this->user = $user;
        $this->password = $password;
    }

    private function callAPI($method, $apiPath, $data = []) {
        $url = "https://{$this->subdomain}.cybozu.com/k/v1{$apiPath}";
        $headers = [
            'Content-Type: application/json',
        ];

        // APIトークンが設定されている場合は、認証ヘッダーを追加
        if (!empty($this->apiToken)) {
            $headers[] = 'X-Cybozu-API-Token: ' . $this->apiToken;
        }

        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

        $response = curl_exec($curl);
        $err = curl_error($curl);

        curl_close($curl);
        
        if ($err) {
            throw new Exception("cURL Error: " . $err);
        } else {
            return json_decode($response, true);
        }
    }

    public function createRecord($appId, $recordData) {
        return $this->callAPI('POST', "/record.json", ['app' => $appId, 'record' => $recordData]);
    }

    public function updateRecord($appId, $recordId, $recordData) {
        return $this->callAPI('PUT', "/record.json", ['app' => $appId, 'id' => $recordId, 'record' => $recordData]);
    }

    public function deleteRecord($appId, $recordIds) {
        return $this->callAPI('DELETE', "/records.json", ['app' => $appId, 'ids' => $recordIds]);
    }
    
    public function getRecords($appId, $query = '', $fields = []) {
    $apiPath = "/records.json";
    $data = [
        'app' => $appId,
        'query' => $query,
        'fields' => $fields,
    ];
    return $this->callAPI('GET', $apiPath, $data);
    }

    public function uploadFile($fileData, $fileName) {
    $url = "https://{$this->subdomain}.cybozu.com/k/v1/file.json";
    $boundary = uniqid();
    $mimeType = $this->getMimeType($fileName);

    // マルチパートのボディを構築
    $body = "--$boundary\r\n";
    $body .= "Content-Disposition: form-data; name=\"file\"; filename=\"$fileName\"\r\n";
    $body .= "Content-Type: $mimeType\r\n\r\n";
    $body .= $fileData . "\r\n";
    $body .= "--$boundary--\r\n";

    $headers = [
        "X-Cybozu-API-Token: {$this->apiToken}",
        "Content-Type: multipart/form-data; boundary=$boundary",
    ];

    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
    curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);

    $response = curl_exec($curl);
    curl_close($curl);

    if (!$response) {
        throw new Exception("Failed to upload file to kintone.");
    }

    $responseArray = json_decode($response, true);
    if (!isset($responseArray["fileKey"])) {
        throw new Exception("No file key returned from kintone.");
    }

    return $responseArray["fileKey"];
}
    private function getMimeType($fileName) {
    // strrposでファイル名の中で最後に出現するドットの位置を見つける
    $lastDotPosition = strrpos($fileName, '.');
    if ($lastDotPosition === false) {
        // ファイル名にドットがない場合は拡張子がないとみなし、'application/octet-stream'を返す
        return 'application/octet-stream';
    }
    // substrでドットの位置以降の文字列(拡張子)を取得し、strtolowerで小文字にする
    $extension = strtolower(substr($fileName, $lastDotPosition + 1));

    // 拡張子に基づいてMIMEタイプを判断
    switch ($extension) {
        case 'jpg':
        case 'jpeg':
            return 'image/jpeg';
        case 'png':
            return 'image/png';
        case 'gif':
            return 'image/gif';
        case 'pdf':
            return 'application/pdf';
        default:
            return 'application/octet-stream'; // 不明なファイル形式の場合
    }
}
}
?> 
            

設定値

<?php
//------------------------------
// 設定値
//------------------------------
//SPIRAL設定値
define("API_URL", "https://api.spiral-platform.com/v1/");
define("API_KEY", "");
define("APP_ROLE", ""); // アプリロールを使用しない場合は未入力
define("APP_ID", "");
define("DB_ID", "");

// kintone設定値以下は使用例です。実際のアプリID、レコードデータ、APIトークンまたはユーザー名/パスワードを適宜設定してください。
$subdomain = ''; // サブドメインを設定
$apiToken = ''; // APIトークンを設定(APIトークン認証を使用する場合 ※基本的にはAPIトークン認証を用いてください。)
$user = 'yourUsername'; // ユーザー名(基本認証を使用する場合)
$password = 'yourPassword'; // パスワード(基本認証を使用する場合)
// キントーンAPIクラスのインスタンスを作成
$kintone = new KintoneAPI($subdomain, $apiToken, $user, $password);
?> 
            

使用例

<?php
//------------------------------
// 設定値
//------------------------------
//SPIRAL設定値
define("API_URL", "https://api.spiral-platform.com/v1/");
define("API_KEY", "");
define("APP_ROLE", ""); // アプリロールを使用しない場合は未入力
define("APP_ID", "");
define("DB_ID", "");

// kintone設定値以下は使用例です。実際のアプリID、レコードデータ、APIトークンまたはユーザー名/パスワードを適宜設定してください。
$subdomain = ''; // サブドメインを設定
$apiToken = ''; // APIトークンを設定(APIトークン認証を使用する場合)
$user = 'yourUsername'; // ユーザー名(基本認証を使用する場合)
$password = 'yourPassword'; // パスワード(基本認証を使用する場合)
// キントーンAPIクラスのインスタンスを作成
$kintone = new KintoneAPI($subdomain, $apiToken, $user, $password);

//------------------------------
// API実行
//------------------------------

// レコードの登録

//SPIRALのレコードをAPIで取得して登録する場合
//SPIRALレコード取得
$commonBase = CommonBase::getInstance();
$recordId = "2";
$resultRecordSelect = $commonBase->apiCurlAction("GET", "/apps/". APP_ID. "/dbs/". DB_ID. "/records/". $recordId);

$recordData = [
    '文字列__1行_' => ['value' => isset($resultRecordSelect["item"]["text"]) ? $resultRecordSelect["item"]["text"] : ''], // テキストフィールド
    'チェックボックス' => [
        'value' => array_map(function($value) use ($resultRecordSelect) { // チェックボックスフィールドの選択肢をラベルに置換
            return isset($resultRecordSelect["options"]["multiSelect"][$value]) ? $resultRecordSelect["options"]["multiSelect"][$value] : $value;
        }, isset($resultRecordSelect["item"]["multiSelect"]) ? $resultRecordSelect["item"]["multiSelect"] : [])
    ],
    'ドロップダウン' => [
        'value' => isset($resultRecordSelect["item"]["select"]) && isset($resultRecordSelect["options"]["select"][$resultRecordSelect["item"]["select"]]) ? 
            $resultRecordSelect["options"]["select"][$resultRecordSelect["item"]["select"]] : '' // ドロップダウンフィールドの選択肢をラベルに置換
    ],
    'ユーザー選択' => [
        'value' => isset($resultRecordSelect["item"]["userSelect"]) ? array_map(function($user) { // ユーザー選択フィールド
            return ['code' => $user]; // ユーザーコードの設定
        }, $resultRecordSelect["item"]["userSelect"]) : []
    ],
];
$response = $kintone->createRecord($appId, $recordData);

//------------------------------
// 共通モジュール
//------------------------------
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);
		}
    }
}
class KintoneAPI {

    private $subdomain;
    private $apiToken;
    private $user;
    private $password;

    public function __construct($subdomain, $apiToken = '', $user = '', $password = '') {
        $this->subdomain = $subdomain;
        $this->apiToken = $apiToken;
        $this->user = $user;
        $this->password = $password;
    }

    private function callAPI($method, $apiPath, $data = []) {
        $url = "https://{$this->subdomain}.cybozu.com/k/v1{$apiPath}";
        $headers = [
            'Content-Type: application/json',
        ];

        // APIトークンが設定されている場合は、認証ヘッダーを追加
        if (!empty($this->apiToken)) {
            $headers[] = 'X-Cybozu-API-Token: ' . $this->apiToken;
        }

        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

        $response = curl_exec($curl);
        $err = curl_error($curl);

        curl_close($curl);
        
        if ($err) {
            throw new Exception("cURL Error: " . $err);
        } else {
            return json_decode($response, true);
        }
    }

    public function createRecord($appId, $recordData) {
        return $this->callAPI('POST', "/record.json", ['app' => $appId, 'record' => $recordData]);
    }

    public function updateRecord($appId, $recordId, $recordData) {
        return $this->callAPI('PUT', "/record.json", ['app' => $appId, 'id' => $recordId, 'record' => $recordData]);
    }

    public function deleteRecord($appId, $recordIds) {
        return $this->callAPI('DELETE', "/records.json", ['app' => $appId, 'ids' => $recordIds]);
    }
    
    public function getRecords($appId, $query = '', $fields = []) {
    $apiPath = "/records.json";
    $data = [
        'app' => $appId,
        'query' => $query,
        'fields' => $fields,
    ];
    return $this->callAPI('GET', $apiPath, $data);
    }

    public function uploadFile($fileData, $fileName) {
    $url = "https://{$this->subdomain}.cybozu.com/k/v1/file.json";
    $boundary = uniqid();
    $mimeType = $this->getMimeType($fileName);

    // マルチパートのボディを構築
    $body = "--$boundary\r\n";
    $body .= "Content-Disposition: form-data; name=\"file\"; filename=\"$fileName\"\r\n";
    $body .= "Content-Type: $mimeType\r\n\r\n";
    $body .= $fileData . "\r\n";
    $body .= "--$boundary--\r\n";

    $headers = [
        "X-Cybozu-API-Token: {$this->apiToken}",
        "Content-Type: multipart/form-data; boundary=$boundary",
    ];

    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
    curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);

    $response = curl_exec($curl);
    curl_close($curl);

    if (!$response) {
        throw new Exception("Failed to upload file to kintone.");
    }

    $responseArray = json_decode($response, true);
    if (!isset($responseArray["fileKey"])) {
        throw new Exception("No file key returned from kintone.");
    }

    return $responseArray["fileKey"];
}
    private function getMimeType($fileName) {
    // strrposでファイル名の中で最後に出現するドットの位置を見つける
    $lastDotPosition = strrpos($fileName, '.');
    if ($lastDotPosition === false) {
        // ファイル名にドットがない場合は拡張子がないとみなし、'application/octet-stream'を返す
        return 'application/octet-stream';
    }
    // substrでドットの位置以降の文字列(拡張子)を取得し、strtolowerで小文字にする
    $extension = strtolower(substr($fileName, $lastDotPosition + 1));

    // 拡張子に基づいてMIMEタイプを判断
    switch ($extension) {
        case 'jpg':
        case 'jpeg':
            return 'image/jpeg';
        case 'png':
            return 'image/png';
        case 'gif':
            return 'image/gif';
        case 'pdf':
            return 'application/pdf';
        default:
            return 'application/octet-stream'; // 不明なファイル形式の場合
    }
}
}
?> 
            
共通モジュールと設定値のソースコードはどのメソッドを実行する時も必要なので設定値はソース上部、共通モジュールはソース下部等に記載してお使いください。

レコード取得

<?php
// レコードの取得
$appId = XX; // アプリID
$query = 'レコード番号 >= 100 order by レコード番号 desc limit 100'; // 取得条件
$fields = ['文字列__1行_', '日付']; // 取得するフィールド

$response = $kintone->getRecords($appId, $query, $fields);
?> 
            
Queryの書き方についてはこちらをご確認ください。

レコード登録

<?php
// レコードの登録
$appId = XX; // アプリID
$recordData = [
    '文字列__1行_' => ['value' => 'テスト'],
    'チェックボックス' => ['value' => ['選択肢1', '選択肢2']], // チェックボックスフィールド
    'ドロップダウン' => ['value' => '選択肢A'], // ドロップダウンフィールド
    'ユーザー選択' => ['value' => [['code' => 'user1']]], // ユーザ選択フィールド
];
$response = $kintone->createRecord($appId, $recordData);
?> 
            
kintoneの仕様ではSPIRALのセレクトやマルチセレクトの値をkintone側ではドロップダウン、チェックボックスで格納する場合はラベル名で挿入する必要があるのでitem配列の当該フィールドをラベル名で置換する処理を記載しています。
ファイルをアップロードする際は後述するファイルアップロードAPIのレスポンスのファイルキーを挿入してください。
ユーザー選択の場合は下記のcodeをIDに合わせた形式にしてください。
    "ユーザー選択": {
      "value": [
        {
          "code": "sato"
        }
      ]
    } 
            
kintone側のフィールドの仕様についてはこちらをご確認ください。

レコード更新

<?php
// レコードの更新
$appId = XX; // アプリID
$recordId = 456; // 更新するレコードのID
$updateData = [
    'fieldCode' => ['value' => 'Updated Value']
];
$response = $kintone->updateRecord($appId, $recordId, $updateData);
?> 
            

レコード削除

<?php
// レコードの削除
$appId = XX; // アプリID
$recordIds = [456, 789]; // 削除するレコードのIDの配列
$response = $kintone->deleteRecord($appId, $recordIds);
?> 
            

ファイルアップロード

<?
// ファイルアップロード
//SPIRALレコード取得
$commonBase = CommonBase::getInstance();
$recordId = "2";
$resultRecordSelect = $commonBase->apiCurlAction("GET", "/apps/". APP_ID. "/dbs/". DB_ID. "/records/". $recordId);
$fileFieldId = "3"; //ダウンロードするファイルフィールドID
$recordId = $resultRecordSelect["item"]["_id"];//ダウンロードするファイルが登録されているレコードID
$fileKey = $resultRecordSelect["item"]["file"][0]["fileKey"];//事前に取得したファイルキー
$apiUrlPass = "/apps/". APP_ID. "/dbs/". DB_ID. "/". $fileFieldId. "/". $recordId. "/files/". $fileKey. "/download";
$apiResponse = $commonBase->apiCurlAction("GET", $apiUrlPass, "", "", "not");

//kintoneAPI送信
$fileData = $apiResponse;
$fileName = $resultRecordSelect["item"]["file"][0]["fileName"];
$response = $kintone->uploadFile($fileData,$fileName);
?> 
            
今回の例ではSPIRALのAPIでファイルを取得した例を記載しています。
フォームブロックの完了ステップ等、SPIRALのレコードを取得する方法は複数ありますので適宜変更してください。

こちらのサンプルを実行しただけではkintoneのレコードにファイルはアップロードされません。
kintoneのファイルアップロードの一時領域にファイルをアップロードしてファイルキーを取得する所までなので、
取得したファイルキーをレコード登録APIに含めてデータを送信する事で初めてレコードとして登録されます。
こちらで取得したファイルキーを前述したレコード登録サンプルと組み合わせてご使用ください。

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