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

 　サンプルデータの作成は [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](https://www.postman.com)

## filemaker-data-api-v2.php

　今回のシナリオに先立ち、以下の修正を施しました。

### executeCurl() メソッド
```
// 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_HTTPHEADER => $curlopt_httpheader,
			CURLOPT_POSTFIELDS => "{}",
		));
	
		$response   = curl_exec($curl);
	
		curl_close($curl);
		return $response;
	}
```

#### getRecords() メソッド

　今回のシナリオに合わせて、アップデートしました。

```
// レコード範囲の取得
	public function getRecords(string $database, string $layout, string $bearer_session_token, int $_offset = 1, int $_limit = 100, string $_sort = ''): string
    {
		$this->layout		= $layout;

		$authorization      = 'Authorization: Bearer ' . $bearer_session_token;
		$curlopt_httpheader = array($authorization);
		$_sort_string		= !empty($_sort) ? '&_sort=' . urlencode($_sort) : '';
		$endpoint        	= "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/layouts/{$layout}/records?_offset={$_offset}&_limit={$_limit}{$_sort_string}";
		return self::executeCurl($endpoint, 'GET', $curlopt_httpheader);
	}
```

#### filemaker-data-api-v2.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, int $_offset = 1, int $_limit = 100, string $_sort = ''): string
    {
		$this->layout		= $layout;

		$authorization      = 'Authorization: Bearer ' . $bearer_session_token;
		$curlopt_httpheader = array($authorization);
		$_sort_string		= !empty($_sort) ? '&_sort=' . urlencode($_sort) : '';
		$endpoint        	= "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/layouts/{$layout}/records?_offset={$_offset}&_limit={$_limit}{$_sort_string}";
		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_HTTPHEADER => $curlopt_httpheader,
			CURLOPT_POSTFIELDS => "{}",
		));
	
		$response   = curl_exec($curl);
	
		curl_close($curl);
		return $response;
	}
}

```

## Get Records のレスポンスの JSON 構造

　前回、リクエストしたレイアウトはポータルのないものでしたので、Get Records のレスポンスの構造は次のようになります。
 
　ここでは、データ件数 2件として、載せています。
```
{
    "response":
    {
        "dataInfo":
        {
            "database":"(FileMaker ソリューション名)",
            "layout":"player_batting_basic",
            "table":"player_batting",
            "totalRecordCount":1375,
            "foundCount":1375,
            "returnedCount":100
        },
        "data":
        [
            {
                "fieldData":
                {
                    "last_name_ first_name":"Hunter, Torii",
                    "player_id":"116338",
                    "year":"2015",
                    "ab":"521",
                    "hit":"125",
                    "single":"81",
                    "double":"22",
                    "triple":"0",
                    "home_run":"22",
                    "on_base_percent":".293"
                },
                "portalData":{},
                "recordId":"1",
                "modId":"0"
            },
            {
                "fieldData":{
                    "last_name_ first_name":"...d",
                    "player_id":"...",
                    "year":"...",
                    "ab":"...",
                    "hit":"...",
                    "single":"...",
                    "double":"...",
                    "triple":"...",
                    "home_run":"...",
                    "on_base_percent":"..."
                },
                "portalData":{},
                "recordId":"2",
                "modId":"0"
            }
        ]
    },
    "messages":
    [
        {
            "code":"0",
            "message":"OK"
        }
    ]
}

```

　大きく分けて、
- `response.dataInfo`
- `response.data`
- `messages`

　この３つになります。`messages`については、これまでと変わりありません。

 `response.dataInfo` の中には、以下の情報が入ります。
 - "database" FileMaker ソリューション名
 - "layout" レイアウト名
 - "table" テーブルオカレンス名
 - "totalRecordCount" リクエストしたレイアウトの総レコード数
 - "foundCOunt" リクエストしたレイアウトの現在のレコード数
 - "returnedCount" リクエストしたレイアウトの現在のレコード中、取得したレコード数

`response.data` は、レコードのフィールドデータとポータルデータ、レコード ID、modId（修正 ID）が、次の形式で入ります。

```
"fieldData":{
    フィールド名:フィールドデータ
},
"portalData":{}, ※ 今回は空
"recordId":"",
"modId":"",
"portalDataInfo": {} ※ 今回は入りません
```

## 取得するデータ件数とソート

### _offset と _limit
　現在、対象にしている player_batting_basic レイアウトのデータは、2015年〜2024年の MLB の打者データのうち、STATCAST の規定に到達している打者の基本的なデータ（STATCAST のデータではなく、従来からある野球データ）を取得しています。

 　前回やった Get Records はデフォルトで、最初の 100 件のみが未ソートの状態で取得されます。

　まず、取得するデータの件数を操作してみましょう。
　これには `_offset` と `_limit` という２つのクエリパラメータを URL に追加します。
　`getRecords()` メソッドで呼んでいる `executeCurl($endpoint, 'GET', $curlopt_httpheader);` の２番目の引数はリクエストメソッドを指定していますが、この `getRecords()` メソッド は **GET** を使っているので、URL の最後に、次の形式で追加します。

```
?_offset=1&_limit=30
```

　この場合、1件目から30件取得という意味になります。
　これで、想定できるのは、例えば、[Bootstrap のページネーションコンポーネント](https://getbootstrap.jp/docs/5.3/components/pagination/) でしょうか。
 　`response.dataInfo.totalRecordCount` でデータ総数は取得できるので、ここからページ数を割り出し、ページネーションコンポーネント を作ることが可能でしょう。

### _sort
　次に取得するデータをソートした状態で欲しい場合です。
 これも、クエリパラメータとして渡します。形式は、以下のようになります。

```
?_sort=[{"fieldName":"フィールド名","sortOrder":"昇順、降順、または値一覧"}]
```

　尚、前のパラメータに続ける場合は、`?` ではなく `&` で繋ぎます。

　ソート対象のフィールド名を知らなければいけませんので、現在のサンプルデータのフィールド名とその意味を掲載します。

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

　このサンプルデータは、CSV をインポートしてテーブルを作っているので、`last_name_ first_name` の間に半角スペースが入っていることに注意してください。（フィールド名は適宜、修正して読み換えてください）

 ここでは、`on_base_percent` でソートすることにします。

#### シナリオ

　**ページネーションを使うことを想定して、1ページ30件で、2ページ目を表示、on_base_percent を降順でソートする。**

#### get_records-v2.php
```
<?php

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

$host                   = 'FileMaker Server ドメイン名';
$version                = 'vLatest';
$database               = 'FileMaker ソリューション名';
$username               = 'FileMaker ユーザ名';
$password               = 'パスワード';
$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);
} else {
    // ログイン失敗の処理を書く
    echo "FileMaker Data API へのログインに失敗しました。\n";
}

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

// セッションの検証
$response   = $fmda->validateSession($bearer_session_token);
if ($fmda->getMessagesCode($response) === '0') {
    // セッション有効なら、行いたいリクエスト実行
    $_offset    = 31;   // 31 件目から
    $_limit     = 30;   // 30 件取得

    $field_name = 'on_base_percent';    // ソート対象フィールド名
    $sort_order = 'descend';    // 昇順 -> ascend、降順 -> descend、値一覧 -> 値一覧名
    $_sort      = [['fieldName' => $field_name, 'sortOrder' => $sort_order]];   // _sort パラメータの配列作成
    $_sort      = json_encode($_sort);  // それを JSON化

    $response   = $fmda->getRecords($database, $layout, $bearer_session_token, $_offset, $_limit, $_sort);
    print_r($response);
    // レコード取得
} else {
    // セッション無効なら、必要な Web データを保持して、ユーザに再ログインを促す
    echo "再ログインしてください。\n";
}

// （何らかの処理）

// ログアウト

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

```

　コメントにも書いていますが、設定と呼び出しは以下のようにします。

 - `$_offset` に取得するレコードの開始位置を指定します。1ページ30件の2ページ目を取得したいので、31 となります
 - `$_limit` に取得するレコードの件数を指定します
 - `$_sort` にパラメータを配列で作り、JOSN 化します
 - `getRecords(FileMaker ソリューション名, レイアウト名, 現在の Bearer Session Token, 開始位置, 件数, ソートJSON)` 形式で呼び出します

 配列は、次の形式です。
```
[
    [
        "fieldName" => "フィールド名",
        "sortOrder" => "ソート方法"
    ]
]
```
　複数のソート条件がある場合は、次のようになります。例えば、プレイヤーID昇順、シーズン降順ならば、

```
[
    [
        "fieldName" => "player_id",
        "sortOrder" => "ascend"
    ],
    [
        "fieldName" => "year",
        "sortOrder" => "descend"
    [
]
```

 これを JSON エンコードして、引数としています。`getRecords()` メソッドでは、この _sort の引数が `''` （空文字）ならば、このパラメータは作成しません。JSON が渡された場合は、URLエンコードして、パラメータを作っています。

　次回は、Get Records で、スクリプトを使う方法を説明します。