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

 サンプルデータの作成は [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.1.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,
		array &$script_name_array,
		array &$script_param_array,
		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) : '';

        $script_prerequest			= '';
    	$script_prerequest_param	= '';
    	$script_presort				= '';
    	$script_presort_param		= '';
    	$script						= '';
    	$script_param				= '';
		// リクエスト処理前のスクリプト
		if ($script_name_array[0] !== '') {
			$script_prerequest	= '&script.prerequest=' . $script_name_array[0];
			$script_prerequest_param = '&script.prerequest.param=' . urlencode(json_encode($script_param_array[0]));
		}
		
		// ソート前のスクリプト
		if ($script_name_array[1] !== '') {
			$script_presort	= '&script.presort=' . $script_name_array[1];
			$script_presort_param = '&script.presort.param=' . urlencode(json_encode($script_param_array[1]));
		}

		// リクエスト処理後のスクリプト
		if ($script_name_array[2] !== '') {
			$script	= '&script=' . $script_name_array[2];
			$script_param = '&script.param=' . urlencode(json_encode($script_param_array[2]));
		}
		
		$endpoint        	= "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/layouts/{$layout}/records?_offset={$_offset}&_limit={$_limit}{$_sort_string}{$script_prerequest}{$script_prerequest_param}{$script_presort}{$script_presort_param}{$script}{$script_param}";
		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;
	}
}

```

## Find Records

 前回まで、records - Get Records を使用してレコードを取得していましたが、今回は、records - Find Records を使用して同じことをやってみたいと思います。

 Get Records と Find Records の顕著な違いは、リクエストメソッドが `GET` か `POST` かということにあります。`GET` である Get Records では、クエリパラメータを URL の一部として渡していました。Find Records は、`POST` なので、URL でクエリパラメータを渡すということはありません。
 Find Records では、JSON形式で、リクエストボディを作り、ポストフィールドとして渡します。
　渡せるクエリパラメータは、ほとんど Get Records と同じです。但し、すべてを JSON 形式で渡すということになります。

 では、まず、filemaker-data-api-v2.1.php に `findRecords()` メソッドを `getRecords()` メソッドの下に追加します。

```
// 検索の実行
	public function findRecords(
		string $database,
		string $layout,
		string $bearer_session_token,
		string $request_body
		): string
	{
		$authorization      = 'Authorization: Bearer ' . $bearer_session_token;
		$content_type       = 'Content-Type: application/json';
		$curlopt_httpheader = array($authorization, $content_type);
		$endpoint        	= "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/layouts/{$layout}/_find";

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

 さらに、`executeCurl()` メソッドもアップデートして、ファイル名を `filemaker-data-api-v2.2.php` とします。
 
```
    // cURL 実行
    static function executeCurl(string $curlopt_url, string $curlopt_customrequest, array $curlopt_httpheader = [], string $current_postfields = ''): string
    {
		$current_postfields	= !empty($current_postfields) ? json_encode(json_decode($current_postfields)) : '{}';

		$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 => $current_postfields,
			CURLOPT_HTTPHEADER => $curlopt_httpheader,
		));
	
		$response   = curl_exec($curl);
	
		curl_close($curl);
		return $response;
	}
    
```

　メインプログラムも、ほとんど変わりませんが、クエリパラメータの作成が大変、楽になります。

### find_records.php
```
<?php

require_once(__DIR__ . '/filemaker-data-api-v2.2.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') {
    // セッション有効なら、行いたいリクエスト実行
    $request_body   = <<<_JSON_
{
    "query" :
    [
        {
            "year" : "2024",
            "omit" : "false"
        }
    ],
    "sort" :
    [
        {
            "fieldName" : "on_base_percent",
            "sortOrder" : "descend"
        }
    ],
    "offset" : "1",
    "limit" : "10"
}
_JSON_;

    // レコードの検索
    $response   = $fmda->findRecords($database, $layout, $bearer_session_token, $request_body);

    // 結果セット処理
    $result = $fmda->json2array($response);
    
    if ($fmda->getMessagesCode($response) === '0') {
        $player_data    = $result['response']['data'];
        foreach ($player_data as $key => $value) {
            echo $value['fieldData']['last_name_ first_name'] . ' - ';
            echo $value['fieldData']['on_base_percent'], PHP_EOL;
        }
    }

    echo "総レコード数: {$result['response']['dataInfo']['totalRecordCount']}\n";
    echo "現在レコード数: {$result['response']['dataInfo']['foundCount']}\n";
    echo "取得レコード数: {$result['response']['dataInfo']['returnedCount']}\n";

} else {
    // セッション無効なら、必要な Web データを保持して、ユーザに再ログインを促す
    echo "再ログインしてください。\n";
}

// （何らかの処理）

// ログアウト

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

```

　リクエストメソッドが `POST` の場合、ポストフィールドに JSON をそのまま渡せばいいので、ソースコード内に JSON をハードコードしています。
 　このままでは、空白、改行が沢山含まれてしまうので、一旦、配列にして、それをまた JSON に戻すという処理を入れて、一気に、空白文字を削除しています。正規表現を使うよりシンプルに変換できると思います。

  JSON を外部ファイルとして持てば、条件に応じて結果セットを変えるという応用もできるでしょう。

　Get Records では、2024年の選手データに絞り込むためスクリプトを使用しましたが、Find Records では、キー `query` の中に、次の形式で、条件を設定していくだけで、同じことが実現できます。

### AND 検索
```
"query" :
    [
        {
            "フィールド名" : "検索する値",
            "フィールド名" : "検索する値",
            "omit" : "false"
        }
    ] 
```

### OR 検索
```
"query" :
    [
        {
            "フィールド名" : "検索する値",
            "omit" : "false"
        },
        {
            "フィールド名r" : "検索する値",
            "omit" : "false"
        }
    ] 
```

 この キー `query` はFind Records リクエストでは必須です。
 キー `omit` は、除外検索のフラグになっています。`true` とすれば、除外検索になります。

　スクリプトも、次の形式で JSON に追加すれば指定できます。
```
"script.prerequest" : "リクエスト処理前に実行するスクリプト名",
"script.prerequest.param" : "リクエスト処理前に実行するスクリプトの引数",
"script.presort" : "ソート実行前に実行するスクリプト名",
"script.presort.param" : "ソート実行前に実行するスクリプトの引数",
"script" : "リクエスト処理後に実行するスクリプト名",
"script.param" : "リクエスト処理後に実行するスクリプトの引数"
```

　考え方は、Get Records のスクリプト指定と同じです。
 
### 完成版 filemaker-data-api-v2.2.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,
		array &$script_name_array,
		array &$script_param_array,
		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) : '';

        $script_prerequest			= '';
    	$script_prerequest_param	= '';
    	$script_presort				= '';
    	$script_presort_param		= '';
    	$script						= '';
    	$script_param				= '';
		// リクエスト処理前のスクリプト
		if ($script_name_array[0] !== '') {
			$script_prerequest	= '&script.prerequest=' . $script_name_array[0];
			$script_prerequest_param = '&script.prerequest.param=' . urlencode(json_encode($script_param_array[0]));
		}
		
		// ソート前のスクリプト
		if ($script_name_array[1] !== '') {
			$script_presort	= '&script.presort=' . $script_name_array[1];
			$script_presort_param = '&script.presort.param=' . urlencode(json_encode($script_param_array[1]));
		}

		// リクエスト処理後のスクリプト
		if ($script_name_array[2] !== '') {
			$script	= '&script=' . $script_name_array[2];
			$script_param = '&script.param=' . urlencode(json_encode($script_param_array[2]));
		}
		
		$endpoint        	= "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/layouts/{$layout}/records?_offset={$_offset}&_limit={$_limit}{$_sort_string}{$script_prerequest}{$script_prerequest_param}{$script_presort}{$script_presort_param}{$script}{$script_param}";
		return self::executeCurl($endpoint, 'GET', $curlopt_httpheader);
	}

	// 検索の実行
	public function findRecords(
		string $database,
		string $layout,
		string $bearer_session_token,
		string $request_body
		): string
	{
		$authorization      = 'Authorization: Bearer ' . $bearer_session_token;
		$content_type       = 'Content-Type: application/json';
		$curlopt_httpheader = array($authorization, $content_type);
		$endpoint        	= "https://{$this->host}/fmi/data/{$this->version}/databases/{$database}/layouts/{$layout}/_find";

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

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

    // レスポンスコードの取得
	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 $current_postfields = ''): string
    {
		$current_postfields	= !empty($current_postfields) ? json_encode(json_decode($current_postfields)) : '{}';

		$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 => $current_postfields,
			CURLOPT_HTTPHEADER => $curlopt_httpheader,
		));
	
		$response   = curl_exec($curl);
	
		curl_close($curl);
		return $response;
	}
}

```