この記事では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に含めてデータを送信する事で初めてレコードとして登録されます。
こちらで取得したファイルキーを前述したレコード登録サンプルと組み合わせてご使用ください。