FileMaker Data API を使う 2025 年版 vol.4

　[FileMaker Data API を使う 2025 年版 vol.3](https://qiita.com/RemixWorksLLC/items/bf4e1ec4bd6a48e75a83) から続きます。

 サンプルデータの作成は [FileMaker Data API を使う 2025 年版 vol.1](https://qiita.com/RemixWorksLLC/items/0e5f2741e7f50547b427) を参照してください。

 ## 利用環境

　以下の環境を前提に説明しています。

 - Claris FileMaker Pro 21.1.1.41 macOS
 - Claris FileMaker Server 21.1.1.40 Ubuntu 22(AMD)
 - サーバ: Ubuntu 22.04.5 LTS
 - SSL 証明書
 - リクエストする側のサーバ（任意）

 　開発段階では、以下を使用した方が楽でしょう。

 - Postman Version 11.30.0

## 今回のシナリオ

　FileMaker Data API のレコード操作には、いくつかのリクエストがあります。また、一つのリクエストでも、いくつかのパターンがあります。
 今回は、その中で、「records - Get Records」を取り上げますが、これにもいくつかのパターンがありますが、以下のようなシナリオを考えてみました。

 1. ログイン
 1. （何らかの処理があり、リンクでページ遷移した想定）
 1. セッションの検証
 1. レコード取得
 1. （何らかの処理）
 1. ログアウト

　「何らかの Web ページ/アプリケーション にログインし、トップページから、レコード取得ページに遷移して、フィルタ操作やダウンロード等の操作をユーザが行い、ログアウトした」というようなシナリオで進めたいと思います。

 ## 事前準備

 　FileMaker Data API でリクエストするレイアウトはリクエストごとに必要なフィールドを並べたものを用意しましょう。
  FileMaker Pro/Go で使用中のレイアウトを使用すると、レスポンスに不要なデータも含まれることになりデータ転送量の無駄になります。また、ポータルが含まれたレイアウトは、レスポンスが複雑になります。
  
  「**レイアウトは必要最小限のフィールドのみを配置する**」

 　これを鉄則にしてください。レイアウトを見せるわけではないので、何の装飾もないただ、雑にフィールドを配置したものでまったく問題ありません。

## 取得するデータの想定

　作成したデータは、1,375 件の STATCAST の PLAYER BATTING の Leaderboard のデータです。42 フィールドありますが、「player_batting」テーブルオカレンスを元にして、以下のフィールドに限定して、レイアウトを作成します。
 　（テーブルオカレンスは、元のテーブルオカレンスを複製し、用途ごとに作成することを推奨しますが、ここではそのまま使用しています。）

 - last_name_ first_name （選手の姓名）
 - player_id （プレーヤーID）
 - year （シーズン）
 - ab (打数)
 - hit （安打）
 - single （単打）
 - double （二塁打）
 - triple （三塁打）
 - home_run （本塁打）
 - on_base_percent （出塁率）

 　レイアウト名は「player_batting_basic」とします。
  
  ![スクリーンショット 2025-02-07 23.51.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3833520/a8642463-2b8d-61b1-dae6-47166845f7a4.png)

## Login 〜 Log out のテンプレート

　ログインとログアウトはリクエスト毎に行う処理なので、クラスか関数にしておくのがいいでしょう。ここでは、簡単にクラスにして、以後、利用していくことにします。

#### filemaker-data-api.php
```
<?php

class FileMaker_Data_API
{
    public $host, $version, $database, $username, $password, $layout;
    public $bearer_session_token;

    function __construct (string $host, string $version)
    {
        $this->host     = $host;
        $this->version  = $version;
    }

    /**
     * リクエスト
     **/

    // ログイン
    public function login (string $database, string $username, string $password): string
    {
		$this->database	= $database;
		$this->username = $username;
		$this->password = $password;

		$auth_value         = $username . ':' . $password;
		$authorization      = 'Authorization: Basic ' . base64_encode($auth_value);
		$content_type       = 'Content-Type: application/json';
		$curlopt_httpheader = array($authorization, $content_type);

		$endpoint        	= "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/sessions";
		
		return self::executeCurl($endpoint, 'POST', $curlopt_httpheader);
	}

    // ログアウト
    public function logOut(string $database, string $bearer_session_token): string
    {
		$endpoint    = "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/sessions/{$bearer_session_token}";

		return self::executeCurl($endpoint, 'DELETE');
	}

    /**
     * ユーティリティ
     **/

    // レスポンスコードの取得
	public function getMessagesCode (string $response): string
    {
		$response_array = self::json2array($response);
		return $response_array['messages'][0]['code'];
	}

    // Bearer Session Token 取得
    public function getBearerSessionToken (string $response): string
    {
		$response_array = self::json2array($response);
		$this->bearer_session_token = $response_array['response']['token'];

		return $this->bearer_session_token;
	}

    // JSON を 配列へ変換
	public function json2array (string $json): array
    {
		$json   = mb_convert_encoding($json, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
		return json_decode($json, true);
	}

    // cURL 実行
    static function executeCurl(string $curlopt_url, string $curlopt_customrequest, array $curlopt_httpheader = []): string
    {
		$curl   = curl_init();

		curl_setopt_array($curl, array(
			CURLOPT_URL => $curlopt_url,
			CURLOPT_RETURNTRANSFER => true,
			CURLOPT_ENCODING => '',
			CURLOPT_MAXREDIRS => 10,
			CURLOPT_TIMEOUT => 0,
			CURLOPT_FOLLOWLOCATION => true,
			CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
			CURLOPT_CUSTOMREQUEST => $curlopt_customrequest,
			CURLOPT_POSTFIELDS => "{}",
			CURLOPT_HTTPHEADER => $curlopt_httpheader,
		));

		$response   = curl_exec($curl);

		curl_close($curl);
		return $response;
	}
}


```

　やや冗長ですが、処理の流れを分かりやすく書いてみました。これを読み込んで、ログインからログアウトまでのテンプレートを書いてみます。

### login-log_out.php
```
<?php

require_once(__DIR__ . '/filemaker-data-api.php');

$host                   = '（FileMaker Server のドメイン名）';
$version                = 'vLatest';
$database               = '（FileMaker ソリューション名）';
$username               = '（FileMaker ユーザ名）';
$password               = '（FileMaker パスワード）';
$layout                 = 'player_batting_basic';
$bearer_session_token   = null;

$fmda   = new FileMaker_Data_API($host, $version);

// ログイン
$response = $fmda->login($database, $username, $password);

// ログイン成功？
if ($fmda->getMessagesCode($response) === '0') {
    // ログイン成功なら、Bearer Session Token を取得する
    $bearer_session_token   = $fmda->getBearerSessionToken($response);
    echo $bearer_session_token, PHP_EOL;
} else {
    // ログイン失敗の処理を書く
    echo "FileMaker Data API へのログインに失敗しました。\n";
}

// （何らかの処理があり、リンクでページ遷移した想定）

// セッションの検証

// レコード取得

// （何らかの処理）

// ログアウト

$response = $fmda->logOut($database, $bearer_session_token);

```

　簡単に説明します。
1. `$fmda   = new FileMaker_Data_API($host, $version);`でクラスのインスタンスを作成します。引数は、FileMaker以後は、`$fmda`がハンドルになります
1. `login()`メソッドを呼びます
1. `response.messages[0].code`のレスポンスコードを取得します。これが 0 ならログイン成功です
1. 成功の場合は、`getBearerSessionToken()` を呼んで、Bearer Session Token を取得します
1. 失敗の場合は、失敗処理をします
1. （何らかの処理があり、リンクでページ遷移した想定）
1. セッションの検証
1. レコード取得
1. （何らかの処理）
1. `logOut()`メソッドを呼んで、ログアウトします

　尚、コーディングにおけるブレース`{}`は、クラス、メソッド、関数の場合は改行を入れて、if等その他の場合は、改行しないで書いています。

　以上が、FileMaker Data API の基本的な流れになります。

 ## Validate Session
 　Web アプリケーションで、ユーザにログイン情報を入力してもらったら、それを何らかの方法で保持しながら、FileMaker Data API のログイン〜処理〜ログアウトを繰り返すことになりますが、ログイン以降のリクエストは、すべて Bearer Session Token を使って行います。

  しかし、Web 閲覧中のユーザの行動は予測できませんので（例えば、戻る動作をしている、長時間放置している等）、何らかのリクエストをする前には、auth - Validate Session を使って、Bearer Session Token が有効であるのかを検証しましょう。

## filemaker-data-api.php への追加コード
```
（logOut メソッドのブロックの下に追加）
    // データベースセッションの検証
	public function validateSession (string $session_token): string
    {
		$authorization      = 'Authorization: Bearer ' . $session_token;
		$curlopt_httpheader = array($authorization);
		$endpoint        	= "https://{$this->host}/fmi/data/{$this->version}/validateSession";

		return self::executeCurl($endpoint, 'GET', $curlopt_httpheader);
	}
```

 `login-log_out.php`の`// セッションの検証`と`// レコード取得`のコメントの部分を書き換えます。
```
// セッションの検証
$response   = $fmda->validateSession($bearer_session_token);
if ($fmda->getMessagesCode($response) === '0') {
    // セッション有効なら、行いたいリクエスト実行
    echo "成功\n";
    // レコード取得
} else {
    // セッション無効なら、必要な Web データを保持して、ユーザに再ログインを促す
    echo "再ログインしてください。\n";
}
```

## Get Records
　セッションを検証したら、メインのリクエストを行います。

 　まず、クラスに、records - Get Recordsのメソッドを追加します。
```
（validateSession メソッドのブロックの下に追加）
    // レコード範囲の取得
	public function getRecords(string $database, string $layout, string $bearer_session_token): string
    {
		$this->layout		= $layout;

		$authorization      = 'Authorization: Bearer ' . $bearer_session_token;
		$curlopt_httpheader = array($authorization);
		$endPoint        	= "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/layouts/{$layout}/records";

		return self::executeCurl($endPoint, 'GET', $curlopt_httpheader);
	}
```

　先程書き換えたセッションの検証のブロックの if の中身を書き換えます。
```
// セッションの検証
$response   = $fmda->validateSession($bearer_session_token);
if ($fmda->getMessagesCode($response) === '0') {
    // セッション有効なら、行いたいリクエスト実行
    $response   = $fmda->getRecords($database, $layout, $bearer_session_token);
    print_r($response);
    // レコード取得
} else {
    // セッション無効なら、必要な Web データを保持して、ユーザに再ログインを促す
    echo "再ログインしてください。\n";
}
```

　成功すると、何やら JSON の塊が表示されますが、一応、Get Records が出来ました。
　しかし、Get Records リクエストには、様々なパラメータを渡せます。また、取得できたのは、1,375件中 Get Records のデフォルトの100件のみです。

 次回は、レスポンスの JSON の構造と Get Records のもう少し、細かい使い方をやってみたいと思います。

### 完成版 filemaker-data-api.php
```
<?php

class FileMaker_Data_API
{
    public $host, $version, $database, $username, $password, $layout;
    public $bearer_session_token;

    function __construct (string $host, string $version)
    {
        $this->host     = $host;
        $this->version  = $version;
    }

    /**
     * リクエスト
     **/

    // ログイン
    public function login (string $database, string $username, string $password): string
    {
		$this->database	= $database;
		$this->username = $username;
		$this->password = $password;

		$auth_value         = $username . ':' . $password;
		$authorization      = 'Authorization: Basic ' . base64_encode($auth_value);
		$content_type       = 'Content-Type: application/json';
		$curlopt_httpheader = array($authorization, $content_type);

		$endpoint        	= "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/sessions";
		
		return self::executeCurl($endpoint, 'POST', $curlopt_httpheader);
	}

    // ログアウト
    public function logOut(string $database, string $bearer_session_token): string
    {
		$endpoint    = "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/sessions/{$bearer_session_token}";

		return self::executeCurl($endpoint, 'DELETE');
	}

    // データベースセッションの検証
	public function validateSession (string $session_token): string
    {
		$authorization      = 'Authorization: Bearer ' . $session_token;
		$curlopt_httpheader = array($authorization);
		$endpoint        	= "https://{$this->host}/fmi/data/{$this->version}/validateSession";

		return self::executeCurl($endpoint, 'GET', $curlopt_httpheader);
	}

    // レコード範囲の取得
	public function getRecords(string $database, string $layout, string $bearer_session_token): string
    {
		$this->layout		= $layout;

		$authorization      = 'Authorization: Bearer ' . $bearer_session_token;
		$curlopt_httpheader = array($authorization);
		$endPoint        	= "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/layouts/{$layout}/records";

		return self::executeCurl($endPoint, 'GET', $curlopt_httpheader);
	}

    /**
     * ユーティリティ
     **/

    // レスポンスコードの取得
	public function getMessagesCode (string $response): string
    {
		$response_array = self::json2array($response);
		return $response_array['messages'][0]['code'];
	}

    // Bearer Session Token 取得
    public function getBearerSessionToken (string $response): string
    {
		$response_array = self::json2array($response);
		$this->bearer_session_token = $response_array['response']['token'];

		return $this->bearer_session_token;
	}

    // JSON を 配列へ変換
	public function json2array (string $json): array
    {
		$json   = mb_convert_encoding($json, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
		return json_decode($json, true);
	}

    // cURL 実行
    static function executeCurl(string $curlopt_url, string $curlopt_customrequest, array $curlopt_httpheader = []): string
    {
		$curl   = curl_init();

		curl_setopt_array($curl, array(
			CURLOPT_URL => $curlopt_url,
			CURLOPT_RETURNTRANSFER => true,
			CURLOPT_ENCODING => '',
			CURLOPT_MAXREDIRS => 10,
			CURLOPT_TIMEOUT => 0,
			CURLOPT_FOLLOWLOCATION => true,
			CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
			CURLOPT_CUSTOMREQUEST => $curlopt_customrequest,
			CURLOPT_POSTFIELDS => "{}",
			CURLOPT_HTTPHEADER => $curlopt_httpheader,
		));

		$response   = curl_exec($curl);

		curl_close($curl);
		return $response;
	}
}
```

### 完成版 get_records.php
```
<?php

require_once(__DIR__ . '/filemaker-data-api.php');

$host                   = '（FileMaker Server のドメイン名）';
$version                = 'vLatest';
$database               = '（FileMaker ソリューション名）';
$username               = '（FileMaker ユーザ名）';
$password               = '（FileMaker パスワード）';
$layout                 = 'player_batting_basic';
$bearer_session_token   = null;

$fmda   = new FileMaker_Data_API($host, $version);

// ログイン
$response = $fmda->login($database, $username, $password);

// ログイン成功？
if ($fmda->getMessagesCode($response) === '0') {
    // ログイン成功なら、Bearer Session Token を取得する
    $bearer_session_token   = $fmda->getBearerSessionToken($response);
    echo $bearer_session_token, PHP_EOL;
} else {
    // ログイン失敗の処理を書く
    echo "FileMaker Data API へのログインに失敗しました。\n";
}

// （何らかの処理があり、リンクでページ遷移した想定）

// セッションの検証
$response   = $fmda->validateSession($bearer_session_token);
if ($fmda->getMessagesCode($response) === '0') {
    // セッション有効なら、行いたいリクエスト実行
    $response   = $fmda->getRecords($database, $layout, $bearer_session_token);
    print_r($response);
    // レコード取得
} else {
    // セッション無効なら、必要な Web データを保持して、ユーザに再ログインを促す
    echo "再ログインしてください。\n";
}

// レコード取得

// （何らかの処理）

// ログアウト

$response = $fmda->logOut($database, $bearer_session_token);
```

### login-log_out.php テンプレート
```
<?php

require_once(__DIR__ . '/filemaker-data-api.php');

$host                   = 'remixworks.cloud';
$version                = 'vLatest';
$database               = 'remixworks_test';
$username               = 'remixworks_1228';
$password               = 'tomikf19';
$layout                 = 'player_batting_basic';
$bearer_session_token   = null;

$fmda   = new FileMaker_Data_API($host, $version);

// 次のメタデータ取得のリクエストはログインする必要がない。
// Product Info
// Database Names

/**
 * Login begin
 */
// ログイン
$response = $fmda->login($database, $username, $password);

// ログイン成功？
if ($fmda->getMessagesCode($response) === '0') {
    // ログイン成功なら、Bearer Session Token を取得する
    $bearer_session_token   = $fmda->getBearerSessionToken($response);
} else {
    // ログイン失敗の処理を書く
    echo "FileMaker Data API へのログインに失敗しました。\n";
}
/**
 * Login end
 */

// （何らかの処理があり、リンクでページ遷移した想定）

// セッションの検証
$response   = $fmda->validateSession($bearer_session_token);
if ($fmda->getMessagesCode($response) === '0') {
    // セッション有効なら、以下の任意のリクエストを実行
    // 取得 Get Records
    // 取得 Get Single Record by Id
    // 作成 Create Record
    // 編集 Edit Record
    // 編集 Upload to Container Field
    // 編集 Upload to Container Field (specific repetition)
    // 編集 Set Global Fields
    // 削除 Delete Record
    // 複製 Duplicate Record
    // 検索 Find Record
    // スクリプト Execute Script
    // メタデータ取得 Layout Names
    // メタデータ取得 Script Names
    // メタデータ取得 Layout Metadata
    // メタデータ取得 Old Layout Metadata
} else {
    // セッション無効なら、必要な Web データを保持して、ユーザに再ログインを促す
    echo "再ログインしてください。\n";
}

// レコード取得

// （何らかの処理）

/**
 * Log out begin
 */
// ログアウト

$response = $fmda->logOut($database, $bearer_session_token);
/**
 * Log out end
 */

```