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クライアントをベースに、さらに機能を拡張することで、より高度なアプリケーション開発が可能になります。