この記事では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); ?>
レコード登録
<?php // レコードの登録 $appId = XX; // アプリID $recordData = [ '文字列__1行_' => ['value' => 'テスト'], 'チェックボックス' => ['value' => ['選択肢1', '選択肢2']], // チェックボックスフィールド 'ドロップダウン' => ['value' => '選択肢A'], // ドロップダウンフィールド 'ユーザー選択' => ['value' => [['code' => 'user1']]], // ユーザ選択フィールド ]; $response = $kintone->createRecord($appId, $recordData); ?>
ファイルをアップロードする際は後述するファイルアップロードAPIのレスポンスのファイルキーを挿入してください。
ユーザー選択の場合は下記のcodeをIDに合わせた形式にしてください。
"ユーザー選択": { "value": [ { "code": "sato" } ] }
レコード更新
<?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のレコードを取得する方法は複数ありますので適宜変更してください。
こちらのサンプルを実行しただけではkintoneのレコードにファイルはアップロードされません。
kintoneのファイルアップロードの一時領域にファイルをアップロードしてファイルキーを取得する所までなので、
取得したファイルキーをレコード登録APIに含めてデータを送信する事で初めてレコードとして登録されます。
こちらで取得したファイルキーを前述したレコード登録サンプルと組み合わせてご使用ください。