RESTful APIを簡単に利用するためのカスタムAPIクライアントのライブラリを作成しました。
カスタムAPIクライアントを使用することで、バックエンドとフロントエンドの連携が容易になり、
データベース操作やメール配信ジョブの管理などの機能を簡単にできるだけでなく、
複雑なAPI呼び出しをシンプルなメソッド呼び出しに抽象化することで、
開発効率の向上とコードの可読性を高めることができます。
注意点
・ エラーハンドリングを適切に行い、ユーザーに分かりやすいメッセージを表示してください。
実装の概要
今回のコードでは、以下の3つの主要コンポーネントで構成されています。
2. カスタムAPIファイル(bridge.php):フロントエンドとバックエンドの橋渡し役
3. JavaScriptクライアント(api-client.js):フロントエンドでAPIを呼び出すためのクラス
ファイルの設置場所
今回のコード紹介するファイルは以下の場所に設置してください。
bridge.php: カスタムAPI
api-client.js: サイトファイル
api-example.js: ページのJavaScript
api-example.html: ページのBODY
実装手順
1. サーバーサイドのAPIクライアント(api.php)をカスタムモジュールに配置
2. フロントエンドとバックエンドの橋渡し役となるカスタムAPIファイル(bridge.php)を配置
3. フロントエンド用のJavaScriptクライアント(api-client.js)をサイトファイルに配置
4. サンプルページ(api-example.html)を作成してクライアントを動作させるJavaScript(api-example.js)をJavaScriptタブに配置
事前準備
PHP APIクライアント(api.php)
<?php
// 設定
define('API_URL', 'https://api.spiral-platform.com/v1/');
define('API_KEY', 'your_api_key_here');
define('DEBUG_MODE', true);
define('API_TIMEOUT', 30);
define('API_RETRY_COUNT', 3);
define('API_RETRY_INTERVAL', 2);
define('DEFAULT_APP_ID', '100');
define('DEFAULT_DB_ID', '1');
/**
* APIクライアント
* リクエスト処理とエンドポイント管理を統合
*/
class Api {
private static $instance = null;
private $lastError = '';
private $lastResponse = null;
private $headers = [];
/**
* コンストラクタ
*/
private function __construct() {
$this->headers = [
'Content-Type: application/json',
'Authorization:Bearer ' . API_KEY,
'X-Spiral-Api-Version: 1.1'
];
}
/**
* インスタンス取得
*/
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* GETリクエスト
*/
public function get($endpoint, $params = []) {
return $this->request('GET', $endpoint, null, $params);
}
/**
* POSTリクエスト
*/
public function post($endpoint, $data = []) {
return $this->request('POST', $endpoint, $data);
}
/**
* PUTリクエスト
*/
public function put($endpoint, $data = []) {
return $this->request('PUT', $endpoint, $data);
}
/**
* PATCHリクエスト
*/
public function patch($endpoint, $data = []) {
return $this->request('PATCH', $endpoint, $data);
}
/**
* DELETEリクエスト
*/
public function delete($endpoint, $params = []) {
return $this->request('DELETE', $endpoint, null, $params);
}
/**
* リクエスト実行
*/
private function request($method, $endpoint, $data = null, $params = []) {
$url = API_URL . $endpoint;
// クエリパラメータの追加
if (!empty($params) && ($method === 'GET' || $method === 'DELETE')) {
$url .= '?' . http_build_query($params);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
curl_setopt($ch, CURLOPT_TIMEOUT, API_TIMEOUT);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
if ($data !== null && ($method === 'POST' || $method === 'PUT' || $method === 'PATCH')) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
$retryCount = 0;
$maxRetries = API_RETRY_COUNT;
while ($retryCount <= $maxRetries) {
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($response !== false) {
$responseData = json_decode($response, true);
$this->lastResponse = [
'response' => [
'info' => ['httpCode' => $httpCode],
'data' => $responseData
]
];
if ($httpCode >= 200 && $httpCode < 300) {
curl_close($ch);
return $this->lastResponse;
}
$this->lastError = "HTTPエラー: {$httpCode}";
$retryCount++;
if ($retryCount <= $maxRetries && $httpCode >= 500) {
sleep(API_RETRY_INTERVAL);
continue;
}
break;
}
$this->lastError = curl_error($ch);
$retryCount++;
if ($retryCount <= $maxRetries) {
sleep(API_RETRY_INTERVAL);
} else {
break;
}
}
curl_close($ch);
return $this->lastResponse;
}
/**
* レスポンスデータ取得
*/
public function getResponseData($response = null) {
if ($response === null) {
$response = $this->lastResponse;
}
if (!$response || !isset($response['response']['data'])) {
return null;
}
return $response['response']['data'];
}
/**
* エラー取得
*/
public function getLastError() {
return $this->lastError;
}
/**
* データベース一覧取得
*/
public function getDatabases($params = [], $appId = null) {
$appId = ($appId === null || $appId === '') ? DEFAULT_APP_ID : $appId;
return $this->get("/apps/{$appId}/dbs", $params);
}
/**
* データベースレコード取得
*/
public function getDatabaseRecords($params = [], $dbId = null, $appId = null) {
$appId = ($appId === null || $appId === '') ? DEFAULT_APP_ID : $appId;
$dbId = $dbId ?? DEFAULT_DB_ID;
return $this->get("/apps/{$appId}/dbs/{$dbId}/records", $params);
}
/**
* データベースレコード作成
*/
public function createDatabaseRecord($data, $dbId = null, $appId = null) {
$appId = ($appId === null || $appId === '') ? DEFAULT_APP_ID : $appId;
$dbId = $dbId ?? DEFAULT_DB_ID;
return $this->post("/apps/{$appId}/dbs/{$dbId}/records", $data);
}
/**
* データベースレコード更新
*/
public function updateDatabaseRecord($recordId, $data, $dbId = null, $appId = null) {
$appId = ($appId === null || $appId === '') ? DEFAULT_APP_ID : $appId;
$dbId = $dbId ?? DEFAULT_DB_ID;
return $this->put("/apps/{$appId}/dbs/{$dbId}/records/{$recordId}", $data);
}
/**
* データベースレコード削除
*/
public function deleteDatabaseRecord($recordId, $dbId = null, $appId = null) {
$appId = ($appId === null || $appId === '') ? DEFAULT_APP_ID : $appId;
$dbId = $dbId ?? DEFAULT_DB_ID;
return $this->delete("/apps/{$appId}/dbs/{$dbId}/records/{$recordId}");
}
/**
* EXPRESSメール配信ジョブ一覧取得
* @param array $params クエリパラメータ(ids, isTest, offset, limit)
* @param string|null $dbId データベースID
* @param string|null $appId アプリID
* @return array レスポンス
*/
public function getEmailJobs($params = [], $dbId = null, $appId = null) {
$appId = ($appId === null || $appId === '') ? DEFAULT_APP_ID : $appId;
$dbId = $dbId ?? DEFAULT_DB_ID;
return $this->get("/apps/{$appId}/dbs/{$dbId}/emailJobs", $params);
}
/**
* データベースのフィールド一覧取得
* @param array $params クエリパラメータ
* @param string|null $dbId データベースID
* @param string|null $appId アプリID
* @return array レスポンス
*/
public function getDatabaseFields($params = [], $dbId = null, $appId = null) {
$appId = ($appId === null || $appId === '') ? DEFAULT_APP_ID : $appId;
$dbId = $dbId ?? DEFAULT_DB_ID;
return $this->get("/apps/{$appId}/dbs/{$dbId}", $params);
}
}
?>
PHPのAPIクライアントは、RESTful APIへのリクエストを簡単に行うためのクラスです。
主な機能として、GET/POST/PUT/DELETEメソッドのリクエスト処理、エラーハンドリング、
そして各種APIエンドポイントへのアクセスメソッドを提供しています。
カスタムAPIファイル(bridge.php)
<?php
require_once 'api.php'; //カスタムモジュールのディレクトリを指定
// APIインスタンス取得
$api = Api::getInstance();
// SPIRALオブジェクトからリクエストデータを取得
$requestBody = $SPIRAL->getCustomApiRequestBody();
$queryParams = $SPIRAL->getCustomApiQueryParameters();
// アクション取得
$action = $queryParams['action'][0] ?? '';
// パラメータ処理
$appId = isset($requestBody['appId']) && $requestBody['appId'] !== '' ? $requestBody['appId'] : null;
$dbId = isset($requestBody['dbId']) && $requestBody['dbId'] !== '' ? $requestBody['dbId'] : null;
// レスポンス初期化
$response = [
'status' => 'success',
'data' => [
'success' => false,
'data' => null,
'error' => null
]
];
try {
// アクションに応じた処理
switch ($action) {
case 'getDatabases':
$params = $requestBody['params'] ?? [];
$result = $api->getDatabases($params, $appId);
$response['data']['success'] = true;
$response['data']['data'] = [
'result' => $api->getResponseData($result),
'requestInfo' => [
'action' => $action,
'appId' => $appId ?? DEFAULT_APP_ID,
'params' => $params
]
];
break;
case 'getDatabaseRecords':
$params = $requestBody['params'] ?? [];
$result = $api->getDatabaseRecords($params, $dbId, $appId);
$response['data']['success'] = true;
$response['data']['data'] = [
'result' => $api->getResponseData($result),
'requestInfo' => [
'action' => $action,
'appId' => $appId ?? DEFAULT_APP_ID,
'dbId' => $dbId ?? DEFAULT_DB_ID,
'params' => $params
]
];
break;
case 'createDatabaseRecord':
$recordData = $requestBody['recordData'] ?? [];
$result = $api->createDatabaseRecord($recordData, $dbId, $appId);
$response['data']['success'] = true;
$response['data']['data'] = [
'result' => $api->getResponseData($result),
'requestInfo' => [
'action' => $action,
'appId' => $appId ?? DEFAULT_APP_ID,
'dbId' => $dbId ?? DEFAULT_DB_ID,
'recordData' => $recordData
]
];
break;
case 'getEmailJobs':
$searchData = $requestBody['searchData'] ?? [];
$result = $api->getEmailJobs($searchData);
$response['data']['success'] = true;
$response['data']['data'] = [
'result' => $api->getResponseData($result),
'requestInfo' => [
'action' => $action,
'searchData' => $searchData
]
];
break;
case 'getDatabaseFields':
$params = $requestBody['params'] ?? [];
$result = $api->getDatabaseFields($params, $dbId, $appId);
$response['data']['success'] = true;
$response['data']['data'] = [
'result' => $api->getResponseData($result),
'requestInfo' => [
'action' => $action,
'appId' => $appId ?? DEFAULT_APP_ID,
'dbId' => $dbId ?? DEFAULT_DB_ID,
'params' => $params
]
];
break;
default:
throw new Exception('不明なアクション: ' . $action);
}
} catch (Exception $e) {
$response['data']['error'] = ['message' => $e->getMessage()];
}
// レスポンス設定
$SPIRAL->setCustomApiResponse($response);
?>
カスタムAPIファイルは、フロントエンドからのリクエストを受け取り、
適切なAPIメソッドを呼び出して結果を返す役割を持ちます。
POSTリクエストのパラメータを解析し、アクションに応じた処理を実行します。
require_once 'api.php'; //カスタムモジュールのディレクトリを指定の部分には、
api.phpを設置したPHPモジュールのディレクトリを指定してください。
JavaScriptクライアント(api-client.js)
const DEFAULT_BRIDGE_PATH = '/_program/bridge';
class ApiClient {
constructor(baseUrl = '/api') {
this.baseUrl = baseUrl;
this.defaultAppId = '';
this.defaultDbId = '';
}
/**
* デフォルトのアプリIDを設定
* @param {string} appId - アプリID
*/
setDefaultAppId(appId) {
this.defaultAppId = appId;
}
/**
* デフォルトのデータベースIDを設定
* @param {string} dbId - データベースID
*/
setDefaultDbId(dbId) {
this.defaultDbId = dbId;
}
/**
* PHPブリッジを使用してAPIリクエストを実行
* @param {string} action - 実行するアクション
* @param {Object} data - リクエストデータ
* @returns {Promise<Object>} レスポンス
*/
async request(action, data = {}) {
const url = `${DEFAULT_BRIDGE_PATH}?action=${action}`;
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTPエラー: ${response.status}`);
}
const result = await response.json();
if (result.status === 'success' && result.data) {
return result.data;
} else {
throw new Error(result.data?.error?.message || '不正なレスポンス形式です');
}
} catch (error) {
console.error('APIリクエストエラー:', error);
return {
success: false,
error: {
message: error.message || 'APIリクエストに失敗しました'
}
};
}
}
/**
* データベース一覧を取得
* @param {Object} params - クエリパラメータ
* @param {string} appId - アプリID(省略可)
* @returns {Promise<Object>} レスポンス
*/
async getDatabases(params = {}, appId = null) {
return this.request('getDatabases', {
appId: appId || this.defaultAppId,
params
});
}
/**
* データベースレコード一覧を取得
* @param {Object} params - クエリパラメータ
* @param {string} dbId - データベースID(省略可)
* @param {string} appId - アプリID(省略可)
* @returns {Promise<Object>} レスポンス
*/
async getDatabaseRecords(params = {}, dbId = null, appId = null) {
return this.request('getDatabaseRecords', {
appId: appId || this.defaultAppId,
dbId: dbId || this.defaultDbId,
params
});
}
/**
* 新しいデータベースレコードを作成
* @param {Object} recordData - レコードデータ
* @param {string} dbId - データベースID(省略可)
* @param {string} appId - アプリID(省略可)
* @returns {Promise<Object>} レスポンス
*/
async createDatabaseRecord(recordData, dbId = null, appId = null) {
return this.request('createDatabaseRecord', {
appId: appId || this.defaultAppId,
dbId: dbId || this.defaultDbId,
recordData
});
}
/**
* EXPRESSメール配信ジョブ一覧を取得
* @param {Object} params - クエリパラメータ(ids, isTest, offset, limit)
* @param {string} dbId - データベースID(省略可)
* @param {string} appId - アプリID(省略可)
* @returns {Promise<Object>} レスポンス
*/
async getEmailJobs(params = {}, dbId = null, appId = null) {
return this.request('getEmailJobs', {
appId: appId || this.defaultAppId,
dbId: dbId || this.defaultDbId,
params
});
}
/**
* データベースのフィールドリストを取得
* @param {Object} params - クエリパラメータ
* @param {string} dbId - データベースID(省略可)
* @param {string} appId - アプリID(省略可)
* @returns {Promise<Object>} レスポンス
*/
async getDatabaseFields(params = {}, dbId = null, appId = null) {
return this.request('getDatabaseFields', {
appId: appId || this.defaultAppId,
dbId: dbId || this.defaultDbId,
params
});
}
}
JavaScriptクライアントは、ブラウザからAPIを簡単に呼び出すためのクラスです。
Fetch APIを使用して非同期リクエストを行い、Promiseベースで結果を返します。
各種APIエンドポイントに対応するメソッドを提供し、エラーハンドリングも行います。
const DEFAULT_BRIDGE_PATH = '/_program/bridge';の部分には、
カスタムAPIファイル(bridge.php)を設置したディレクトリを指定してください。
サンプル実装(api-example.js:ページのJavaScript)
// APIクライアントのインスタンスを作成
let api;
// api-client.jsが読み込まれているか確認する関数
function checkApiClientLoaded() {
if (typeof ApiClient === 'undefined') {
console.error('ApiClientが読み込まれていません。api-client.jsが正しく読み込まれているか確認してください。');
// 500ミリ秒後に再試行
setTimeout(checkApiClientLoaded, 500);
return false;
}
console.log('ApiClientが正常に読み込まれました');
// ApiClientが利用可能になったらインスタンスを作成
api = new ApiClient();
// DOM要素の参照を取得し、イベントリスナーを設定
initializeApp();
return true;
}
// DOM要素の参照
let outputDiv;
let loadingIndicator;
/**
* アプリケーションの初期化
*/
function initializeApp() {
outputDiv = document.getElementById('output');
loadingIndicator = document.getElementById('loading');
// イベントリスナーを設定
setupEventListeners();
}
/**
* 結果を表示
* @param {string} title - タイトル
* @param {Object} data - 表示データ
*/
function displayResult(title, data) {
const resultDiv = document.createElement('div');
resultDiv.className = 'result-item';
const titleElement = document.createElement('h3');
titleElement.textContent = title;
resultDiv.appendChild(titleElement);
const contentElement = document.createElement('pre');
contentElement.className = 'json-content';
contentElement.textContent = JSON.stringify(data, null, 2);
resultDiv.appendChild(contentElement);
outputDiv.prepend(resultDiv);
}
/**
* エラーを表示
* @param {string} title - タイトル
* @param {Object|string} error - エラー情報
*/
function displayError(title, error) {
const errorDiv = document.createElement('div');
errorDiv.className = 'result-item error';
const titleElement = document.createElement('h3');
titleElement.textContent = title;
errorDiv.appendChild(titleElement);
const contentElement = document.createElement('pre');
contentElement.className = 'error-content';
if (typeof error === 'string') {
contentElement.textContent = error;
} else {
contentElement.textContent = JSON.stringify(error, null, 2);
}
errorDiv.appendChild(contentElement);
outputDiv.prepend(errorDiv);
}
/**
* ローディング表示の切り替え
* @param {boolean} isLoading - ローディング中かどうか
*/
function toggleLoading(isLoading) {
loadingIndicator.style.display = isLoading ? 'block' : 'none';
}
// ===== サンプル実行関数 =====
/**
* データベース一覧取得のサンプル
* @param {string} appId - アプリID
*/
async function getDatabasesExample(appId = '') {
toggleLoading(true);
try {
const result = await api.getDatabases({}, appId);
if (result.status === "success") {
displayResult('データベース一覧取得', result.data.data.result);
} else {
displayError('データベース一覧取得エラー', result.error);
}
} catch (error) {
displayError('データベース一覧取得例外', error);
}
toggleLoading(false);
}
/**
* データベースレコード一覧取得のサンプル
* @param {string} appId - アプリID
* @param {string} dbId - データベースID
*/
async function getDatabaseRecordsExample(appId = '', dbId = '') {
toggleLoading(true);
try {
const params = {
limit: 10,
offset: 0
};
const result = await api.getDatabaseRecords(params, dbId, appId);
if (result.status === "success") {
displayResult('データベースレコード一覧取得', result.data.data.result);
} else {
displayError('データベースレコード一覧取得エラー', result.error);
}
} catch (error) {
displayError('データベースレコード一覧取得例外', error);
}
toggleLoading(false);
}
/**
* 新規データベースレコード作成のサンプル
* @param {string} appId - アプリID
* @param {string} dbId - データベースID
*/
async function createDatabaseRecordExample(appId = '', dbId = '') {
toggleLoading(true);
try {
const recordData = {
email: `test${Math.floor(Math.random() * 1000000)}@example.com`,
firstName: 'テスト',
lastName: 'ユーザー',
};
const result = await api.createDatabaseRecord(recordData, dbId, appId);
if (result.status === "success") {
displayResult('データベースレコード作成', result.data.data.result);
} else {
displayError('データベースレコード作成エラー', result.error);
}
} catch (error) {
displayError('データベースレコード作成例外', error);
}
toggleLoading(false);
}
/**
* EXPRESSメール配信ジョブ一覧取得のサンプル
* @param {string} appId - アプリID
* @param {string} dbId - データベースID
*/
async function getEmailJobsExample(appId = '', dbId = '') {
toggleLoading(true);
try {
const params = {
limit: 20,
offset: 0,
isTest: false
};
const result = await api.getEmailJobs(params, dbId, appId);
if (result.status === "success") {
displayResult('メール配信ジョブ一覧取得', result.data.data.result);
} else {
displayError('メール配信ジョブ一覧取得エラー', result.error);
}
} catch (error) {
displayError('メール配信ジョブ一覧取得例外', error);
}
toggleLoading(false);
}
/**
* データベースのフィールドリスト取得のサンプル
* @param {string} appId - アプリID
* @param {string} dbId - データベースID
*/
async function getDatabaseFieldsExample(appId = '', dbId = '') {
toggleLoading(true);
try {
const params = {};
const result = await api.getDatabaseFields(params, dbId, appId);
if (result.status === "success") {
// レスポンスデータの構造を正しく処理
let fields = [];
// 入れ子になったデータ構造を適切に処理
if (result.data && result.data.data && result.data.data.result && result.data.data.result.fields) {
fields = result.data.data.result.fields;
displayResult('データベースフィールド一覧取得', fields);
// フィールドに基づいて動的フォームを生成
generateDynamicForm(fields);
} else {
displayError('データベースフィールド一覧取得エラー', 'フィールドデータが見つかりません');
}
} else {
displayError('データベースフィールド一覧取得エラー', result.error);
}
} catch (error) {
displayError('データベースフィールド一覧取得例外', error);
}
toggleLoading(false);
}
/**
* フィールドリストに基づいて動的フォームを生成
* @param {Array} fields - フィールドリスト
*/
function generateDynamicForm(fields) {
// 既存のフォームがあれば削除
const existingForm = document.getElementById('dynamic-form-container');
if (existingForm) {
existingForm.remove();
}
// 新しいフォームコンテナを作成
const formContainer = document.createElement('div');
formContainer.id = 'dynamic-form-container';
formContainer.className = 'result-item';
const formTitle = document.createElement('h3');
formTitle.textContent = 'レコード登録フォーム';
formContainer.appendChild(formTitle);
// フォーム要素を作成
const form = document.createElement('form');
form.id = 'record-form';
// フィールドごとに入力要素を追加
fields.forEach(field => {
const fieldContainer = document.createElement('div');
fieldContainer.className = 'form-field';
const label = document.createElement('label');
label.setAttribute('for', `field-${field.name}`);
label.textContent = `${field.displayName || field.name}${field.required ? ' *' : ''}`;
fieldContainer.appendChild(label);
// フィールドタイプに応じた入力要素を作成
let input;
switch (field.type) {
case 'select':
// 通常のセレクトボックス
input = document.createElement('select');
// 空のオプションを追加
const emptyOption = document.createElement('option');
emptyOption.value = '';
emptyOption.textContent = '選択してください';
input.appendChild(emptyOption);
// 選択肢を追加
if (field.options && Array.isArray(field.options)) {
field.options.forEach(option => {
const optionElement = document.createElement('option');
optionElement.value = option.value || option.id || '';
optionElement.textContent = option.label || option.name || option.value || '';
input.appendChild(optionElement);
});
}
break;
case 'multiselect':
// マルチセレクトの場合はチェックボックスグループとして実装
// チェックボックスグループのコンテナ
const checkboxContainer = document.createElement('div');
checkboxContainer.className = 'checkbox-group';
// 選択肢を追加
if (field.options && Array.isArray(field.options)) {
field.options.forEach(option => {
const checkboxWrapper = document.createElement('div');
checkboxWrapper.className = 'checkbox-item';
// チェックボックス作成
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = `field-${field.name}-${option.value || option.id || ''}`;
checkbox.name = field.name;
checkbox.value = option.value || option.id || '';
checkbox.className = 'checkbox-input';
// ラベル作成
const checkboxLabel = document.createElement('label');
checkboxLabel.className = 'checkbox-label';
checkboxLabel.setAttribute('for', checkbox.id);
checkboxLabel.textContent = option.label || option.name || option.value || '';
// 追加
checkboxWrapper.appendChild(checkboxLabel);
checkboxWrapper.appendChild(checkbox);
checkboxContainer.appendChild(checkboxWrapper);
});
}
input = checkboxContainer;
break;
case 'textarea':
input = document.createElement('textarea');
input.rows = 4;
break;
case 'date':
input = document.createElement('input');
input.type = 'date';
break;
case 'datetime':
input = document.createElement('input');
input.type = 'datetime-local';
break;
case 'number':
input = document.createElement('input');
input.type = 'number';
break;
case 'email':
input = document.createElement('input');
input.type = 'email';
break;
case 'tel':
input = document.createElement('input');
input.type = 'tel';
break;
case 'password':
input = document.createElement('input');
input.type = 'password';
break;
default:
input = document.createElement('input');
input.type = 'text';
}
// 共通属性を設定
input.id = `field-${field.name}`;
if (field.type !== 'multiselect') {
input.name = field.name;
}
input.className = 'form-input';
if (field.required) {
input.required = true;
}
if (field.placeholder) {
input.placeholder = field.placeholder;
}
fieldContainer.appendChild(input);
form.appendChild(fieldContainer);
});
// 送信ボタンを追加
const submitButton = document.createElement('button');
submitButton.type = 'submit';
submitButton.textContent = 'レコードを登録';
submitButton.className = 'submit-button';
form.appendChild(submitButton);
// フォーム送信イベントを設定
form.addEventListener('submit', handleFormSubmit);
formContainer.appendChild(form);
outputDiv.prepend(formContainer);
}
/**
* フォームの送信処理
* @param {SubmitEvent} event - 送信イベント
*/
function handleFormSubmit(event) {
event.preventDefault();
const formData = {};
const formElements = event.target.elements;
// フォームデータの収集
for (let i = 0; i < formElements.length; i++) {
const element = formElements[i];
// ボタン以外の要素を処理
if (element.tagName !== 'BUTTON') {
// チェックボックスの場合は選択された値のみを配列で収集
if (element.type === 'checkbox') {
if (element.checked) {
// 既に同じ名前のプロパティが存在するか確認
if (formData[element.name]) {
// 既に配列の場合は値を追加
if (Array.isArray(formData[element.name])) {
formData[element.name].push(element.value);
} else {
// 既存の値と新しい値で配列を作成
formData[element.name] = [formData[element.name], element.value];
}
} else {
// 新しいプロパティとして値を設定
formData[element.name] = element.value;
}
}
} else {
// その他の入力要素
formData[element.name] = element.value;
}
}
}
console.log('送信データ:', formData);
// APIリクエストの実行
submitDynamicForm(formData);
}
/**
* 動的フォームからのデータを送信
* @param {Object} formData - フォームデータ
*/
async function submitDynamicForm(formData) {
toggleLoading(true);
try {
const appId = document.getElementById('api-app-id').value;
const dbId = document.getElementById('api-db-id').value;
const result = await api.createDatabaseRecord(formData, dbId, appId);
if (result.status === "success") {
displayResult('動的フォームからのレコード登録', result.data.data.result);
// レスポンスの構造を確認して適切なメッセージを表示
if (result.data && result.data.data && result.data.data.result) {
const apiResult = result.data.data.result;
// エラーの場合
if (apiResult.status && apiResult.status === 400) {
let errorMessage = `登録エラー: ${apiResult.message}\n\n`;
if (apiResult.errors && apiResult.errors.length > 0) {
errorMessage += '詳細エラー:\n';
apiResult.errors.forEach(error => {
errorMessage += `・${error.location}: ${error.message}\n`;
});
}
alert(errorMessage);
}
// 成功の場合
else if (apiResult.item) {
alert(`レコードが正常に登録されました。\nレコードID: ${apiResult.item._id}`);
}
// その他の場合
else {
alert('処理は完了しましたが、予期しない応答形式です。詳細は実行結果を確認してください。');
}
} else {
alert('処理は完了しましたが、応答データの形式が不明です。詳細は実行結果を確認してください。');
}
} else {
displayError('レコード登録エラー', result.error);
alert(`レコード登録に失敗しました: ${result.error?.message || '不明なエラー'}`);
}
} catch (error) {
displayError('レコード登録例外', error);
alert(`レコード登録中に例外が発生しました: ${error.message || '不明なエラー'}`);
}
toggleLoading(false);
}
/**
* イベントリスナーを設定
*/
function setupEventListeners() {
// 設定保存ボタン
document.getElementById('btn-save-config').addEventListener('click', () => {
const appId = document.getElementById('api-app-id').value.trim();
const dbId = document.getElementById('api-db-id').value.trim();
if (appId) {
api.setDefaultAppId(appId);
}
if (dbId) {
api.setDefaultDbId(dbId);
}
alert('API設定を保存しました');
});
// データベース一覧取得ボタン
document.getElementById('btn-get-databases').addEventListener('click', () => {
const appId = document.getElementById('api-app-id').value.trim();
getDatabasesExample(appId);
});
// データベースレコード取得ボタン
document.getElementById('btn-get-records').addEventListener('click', () => {
const appId = document.getElementById('api-app-id').value.trim();
const dbId = document.getElementById('api-db-id').value.trim();
getDatabaseRecordsExample(appId, dbId);
});
// メール配信ジョブ一覧取得ボタン
document.getElementById('btn-get-email-jobs').addEventListener('click', () => {
const appId = document.getElementById('api-app-id').value.trim();
const dbId = document.getElementById('api-db-id').value.trim();
getEmailJobsExample(appId, dbId);
});
// フィールドリスト取得ボタン
document.getElementById('btn-get-fields').addEventListener('click', () => {
const appId = document.getElementById('api-app-id').value.trim();
const dbId = document.getElementById('api-db-id').value.trim();
getDatabaseFieldsExample(appId, dbId);
});
}
// DOMが読み込まれたら実行
document.addEventListener('DOMContentLoaded', () => {
// api-client.jsが読み込まれているか確認
checkApiClientLoaded();
});
サンプル実装(api-example.html:ページのBODY)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>REST APIクライアント サンプル</title>
<style>
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
h1, h2 {
color: #2c3e50;
}
.container {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.control-panel {
flex: 1;
min-width: 300px;
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.output-panel {
flex: 2;
min-width: 500px;
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
max-height: 800px;
overflow-y: auto;
}
.button-group {
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
button {
background-color: #3498db;
color: white;
border: none;
padding: 8px 15px;
margin-right: 10px;
margin-bottom: 10px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
button:hover {
background-color: #2980b9;
}
input, select {
padding: 8px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 4px;
width: 100%;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.result-item {
margin-bottom: 20px;
padding: 15px;
border-radius: 4px;
background-color: #f9f9f9;
border-left: 4px solid #3498db;
}
.result-item.error {
border-left-color: #e74c3c;
background-color: #fdf0ed;
}
.result-item h3 {
margin-top: 0;
color: #2c3e50;
}
pre {
background-color: #f5f5f5;
padding: 10px;
border-radius: 4px;
overflow-x: auto;
font-size: 14px;
line-height: 1.4;
}
.json-content {
color: #333;
}
.error-content {
color: #e74c3c;
}
#loading {
display: none;
text-align: center;
margin: 20px 0;
}
.spinner {
border: 4px solid rgba(0, 0, 0, 0.1);
border-radius: 50%;
border-top: 4px solid #3498db;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.checkbox-group {
margin-bottom: 15px;
}
.checkbox-item {
margin-bottom: 10px;
}
.checkbox-item label {
margin-left: 5px;
}
/* マルチセレクト(チェックボックスグループ)のスタイル */
.checkbox-group {
display: flex;
flex-direction: column;
margin-bottom: 15px;
padding: 5px;
width: 100%;
}
.checkbox-item {
display: flex;
align-items: center;
margin-bottom: 10px;
padding: 5px;
width: 100%;
}
.checkbox-item:hover {
background-color: #f5f5f5;
border-radius: 4px;
}
.checkbox-input {
margin-right: 10px;
cursor: pointer;
width:50%;
}
.checkbox-label {
cursor: pointer;
flex: 1;
}
</style>
</head>
<body>
<h1>REST APIクライアント サンプル</h1>
<p>このページでは、JavaScript APIクライアントの使用例を示します。各ボタンをクリックして、対応するAPIエンドポイントを呼び出すことができます。</p>
<div class="container">
<div class="control-panel">
<h2>API設定</h2>
<div>
<label for="api-app-id">アプリID</label>
<input type="text" id="api-app-id" placeholder="アプリID(省略可)">
</div>
<div>
<label for="api-db-id">データベースID</label>
<input type="text" id="api-db-id" placeholder="データベースID(省略可)">
</div>
<button id="btn-save-config">設定を保存</button>
<h2>APIサンプル</h2>
<div class="button-group">
<h3>データベース操作</h3>
<button id="btn-get-databases">データベース一覧取得</button>
<button id="btn-get-records">データベースレコード取得</button>
<button id="btn-get-fields">フィールドリスト取得</button>
</div>
<div class="button-group">
<h3>メール配信</h3>
<button id="btn-get-email-jobs">メール配信ジョブ一覧取得</button>
</div>
</div>
<div class="output-panel">
<h2>実行結果</h2>
<div id="loading">
<div class="spinner"></div>
<p>処理中...</p>
</div>
<div id="output"></div>
</div>
</div>
<script src="/_media/api-client.js"></script>
</body>
</html>
サンプル実装では、APIクライアントを使用して以下の操作を行う例を示しています:
- データベース一覧の取得
- データベースレコードの取得
- データベースレコードの作成
- メール配信ジョブ一覧の取得
HTMLファイル内のJavaScriptコードはサイトファイルに設置して読み込んでください。
主要な機能
メール配信管理: メール配信ジョブの一覧取得
エラーハンドリング: APIリクエスト時のエラー処理と適切なメッセージ表示
非同期処理: Promiseベースの非同期リクエスト処理
実装のポイント
1. 抽象化: 複雑なAPI呼び出しをシンプルなメソッド呼び出しに抽象化
2. エラーハンドリング: リクエスト失敗時の適切なエラー処理
3. 統一インターフェース: バックエンドとフロントエンドで一貫したインターフェース設計
4. 拡張性: 新しいAPIエンドポイントを簡単に追加できる設計
EXPRESSメール配信ジョブ一覧取得の実装例
例でEXPRESSメール配信ジョブ一覧を取得する機能を追加しました。
この機能は、指定したアプリIDとデータベースIDに関連するメール配信ジョブの一覧を取得します。
public function getEmailJobs($params = [], $dbId = null, $appId = null) {
$appId = ($appId === null || $appId === '') ? DEFAULT_APP_ID : $appId;
$dbId = $dbId ?? DEFAULT_DB_ID;
return $this->get("/apps/{$appId}/dbs/{$dbId}/emailJobs", $params);
}
JavaScript側の実装:
async getEmailJobs(params = {}, dbId = null, appId = null) {
return this.request('getEmailJobs', {
appId: appId || this.defaultAppId,
dbId: dbId || this.defaultDbId,
params
});
}
使用例
以下のコードで、メール配信ジョブ一覧を取得できます:
// APIクライアントのインスタンスを作成
const api = new ApiClient();
// メール配信ジョブ一覧を取得
async function getEmailJobsExample(appId = '', dbId = '') {
toggleLoading(true);
try {
const params = {
limit: 20,
offset: 0,
isTest: false
};
const result = await api.getEmailJobs(params, dbId, appId);
if (result.status === "success") {
displayResult('メール配信ジョブ一覧取得', result.data.data.result);
} else {
displayError('メール配信ジョブ一覧取得エラー', result.error);
}
} catch (error) {
displayError('メール配信ジョブ一覧取得例外', error);
}
toggleLoading(false);
}
APIの追加の仕方
新しいAPI機能を追加する場合、以下の3つのファイルを修正する必要があります。
ここでは例として、「getEmailJobs」APIを追加する手順を紹介します。
/**
* メール配信ジョブ一覧取得
*/
public function getEmailJobs($params = [], $dbId = null, $appId = null) {
$appId = ($appId === null || $appId === '') ? DEFAULT_APP_ID : $appId;
$dbId = $dbId ?? DEFAULT_DB_ID;
return $this->get("/apps/{$appId}/dbs/{$dbId}/emailJobs", $params);
}
/**
* メール配信ジョブ一覧を取得
* @param {Object} params - クエリパラメータ(ids, isTest, offset, limit)
* @param {string} dbId - データベースID(省略可)
* @param {string} appId - アプリID(省略可)
* @returns {Promise<Object>} レスポンス
*/
async getEmailJobs(params = {}, dbId = null, appId = null) {
return this.request('getEmailJobs', {
appId: appId || this.defaultAppId,
dbId: dbId || this.defaultDbId,
params
});
}
case 'getEmailJobs':
$searchData = $requestBody['searchData'] ?? [];
$result = $api->getEmailJobs($searchData);
$response['data']['success'] = true;
$response['data']['data'] = [
'result' => $api->getResponseData($result),
'requestInfo' => [
'action' => $action,
'searchData' => $searchData
]
];
break;
APIを追加する際のポイント
2. パラメータの標準化: appId、dbIdなどの共通パラメータは一貫した扱い方をする
3. エラーハンドリング: 適切なエラー処理を実装し、クライアントに分かりやすいメッセージを返す
4. ドキュメント化: 新しいAPIメソッドには適切なコメントを付け、使用方法を明確にする
まとめ
PHPとJavaScriptを組み合わせることで、バックエンドとフロントエンドの連携を効率化し、
データベース操作やメール配信ジョブの管理などの機能を簡単に実装できます。
このAPIクライアントをベースに、さらに機能を拡張することで、より高度なアプリケーション開発が可能になります。
